fix: [quotes] 견적 등록 UI 개선 — 레이아웃/스타일 정리, 입력 영역 높이 맞춤

This commit is contained in:
유병철
2026-03-19 21:06:58 +09:00
parent eb121c3ce6
commit 7dccaf7bab
6 changed files with 129 additions and 112 deletions

View File

@@ -304,9 +304,9 @@ export function LocationDetailPanel({
return ( return (
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* ②-1 개소 정보 영역 */} {/* ②-1 개소 정보 영역 */}
<div className="bg-gray-50 border-b"> <div>
{/* 1행: 층, 부호, 가로, 세로, 제품코드 */} {/* 입력 영역 (왼쪽 입력 폼과 높이 맞춤) */}
<div className="px-4 py-3 space-y-3"> <div className="bg-gray-100 p-4 space-y-3 border-b border-gray-200 min-h-[128px]">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
<div> <div>
<label className="text-xs text-gray-600"></label> <label className="text-xs text-gray-600"></label>
@@ -315,7 +315,7 @@ export function LocationDetailPanel({
value={location.floor} value={location.floor}
onChange={(e) => handleFieldChange("floor", e.target.value)} onChange={(e) => handleFieldChange("floor", e.target.value)}
disabled={disabled} disabled={disabled}
className="h-8 text-sm" className="h-8 text-sm border border-gray-300"
/> />
<datalist id="floorOptionListDetail"> <datalist id="floorOptionListDetail">
{FLOOR_OPTIONS.map((f) => ( {FLOOR_OPTIONS.map((f) => (
@@ -330,7 +330,7 @@ export function LocationDetailPanel({
value={location.code} value={location.code}
onChange={(e) => handleFieldChange("code", e.target.value)} onChange={(e) => handleFieldChange("code", e.target.value)}
disabled={disabled} disabled={disabled}
className="h-8 text-sm" className="h-8 text-sm border border-gray-300"
/> />
<datalist id="locationCodeListDetail"> <datalist id="locationCodeListDetail">
{locationCodes.map((code) => ( {locationCodes.map((code) => (
@@ -344,7 +344,7 @@ export function LocationDetailPanel({
value={location.openWidth} value={location.openWidth}
onChange={(value) => handleFieldChange("openWidth", value ?? 0)} onChange={(value) => handleFieldChange("openWidth", value ?? 0)}
disabled={disabled} disabled={disabled}
className="h-8 text-sm" className="h-8 text-sm border border-gray-300"
/> />
</div> </div>
<div> <div>
@@ -353,7 +353,7 @@ export function LocationDetailPanel({
value={location.openHeight} value={location.openHeight}
onChange={(value) => handleFieldChange("openHeight", value ?? 0)} onChange={(value) => handleFieldChange("openHeight", value ?? 0)}
disabled={disabled} disabled={disabled}
className="h-8 text-sm" className="h-8 text-sm border border-gray-300"
/> />
</div> </div>
<div> <div>
@@ -372,7 +372,7 @@ export function LocationDetailPanel({
}} }}
disabled={disabled} disabled={disabled}
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -398,7 +398,7 @@ export function LocationDetailPanel({
onValueChange={(value) => handleFieldChange("guideRailType", value)} onValueChange={(value) => handleFieldChange("guideRailType", value)}
disabled disabled
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -419,7 +419,7 @@ export function LocationDetailPanel({
onValueChange={(value) => handleFieldChange("motorPower", value)} onValueChange={(value) => handleFieldChange("motorPower", value)}
disabled={disabled} disabled={disabled}
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -440,7 +440,7 @@ export function LocationDetailPanel({
onValueChange={(value) => handleFieldChange("controller", value)} onValueChange={(value) => handleFieldChange("controller", value)}
disabled={disabled} disabled={disabled}
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -454,27 +454,28 @@ export function LocationDetailPanel({
</div> </div>
</div> </div>
{/* 3행: 제작사이즈, 산출중량, 산출면적, 수량, 산출하기 */} </div>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 text-sm pt-2 border-t border-gray-200"> {/* 결과행: 제작사이즈, 산출중량, 산출면적, 수량, 산출하기 */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 text-sm px-4 py-3 bg-gray-50 border-b border-gray-200">
<div> <div>
<span className="text-xs text-gray-500"></span> <span className="text-xs text-muted-foreground"></span>
<p className="font-semibold"> <p className="text-base font-bold">
{String(location.bomResult?.variables?.W1 ?? location.manufactureWidth ?? "-")} {String(location.bomResult?.variables?.W1 ?? location.manufactureWidth ?? "-")}
X X
{String(location.bomResult?.variables?.H1 ?? location.manufactureHeight ?? "-")} {String(location.bomResult?.variables?.H1 ?? location.manufactureHeight ?? "-")}
</p> </p>
</div> </div>
<div> <div>
<span className="text-xs text-gray-500"></span> <span className="text-xs text-muted-foreground"></span>
<p className="font-semibold"> <p className="text-base font-bold">
{(location.bomResult?.variables?.K ?? location.bomResult?.variables?.WEIGHT) {(location.bomResult?.variables?.K ?? location.bomResult?.variables?.WEIGHT)
? `${Number(location.bomResult?.variables?.K ?? location.bomResult?.variables?.WEIGHT).toFixed(2)} kg` ? `${Number(location.bomResult?.variables?.K ?? location.bomResult?.variables?.WEIGHT).toFixed(2)} kg`
: "-"} : "-"}
</p> </p>
</div> </div>
<div> <div>
<span className="text-xs text-gray-500"></span> <span className="text-xs text-muted-foreground"></span>
<p className="font-semibold"> <p className="text-base font-bold">
{(location.bomResult?.variables?.M ?? location.bomResult?.variables?.AREA) {(location.bomResult?.variables?.M ?? location.bomResult?.variables?.AREA)
? `${Number(location.bomResult?.variables?.M ?? location.bomResult?.variables?.AREA).toFixed(2)}` ? `${Number(location.bomResult?.variables?.M ?? location.bomResult?.variables?.AREA).toFixed(2)}`
: "-"} : "-"}
@@ -493,7 +494,7 @@ export function LocationDetailPanel({
totalPrice: unitPrice * newQty, totalPrice: unitPrice * newQty,
}); });
}} }}
className="h-8 text-sm font-semibold" className="h-8 text-sm font-semibold border border-gray-300"
min={1} min={1}
disabled={disabled} disabled={disabled}
/> />
@@ -502,7 +503,7 @@ export function LocationDetailPanel({
<Button <Button
onClick={() => onCalculateLocation?.(location.id)} onClick={() => onCalculateLocation?.(location.id)}
disabled={disabled || isCalculating} disabled={disabled || isCalculating}
className="w-full h-8 bg-orange-500 hover:bg-orange-600 text-white" className="w-full h-8"
> >
{isCalculating ? ( {isCalculating ? (
<> <>
@@ -519,7 +520,6 @@ export function LocationDetailPanel({
</div> </div>
</div> </div>
</div> </div>
</div>
{/* ②-2 품목 상세 영역 */} {/* ②-2 품목 상세 영역 */}
<div className="flex-1 overflow-hidden flex flex-col"> <div className="flex-1 overflow-hidden flex flex-col">
@@ -531,7 +531,7 @@ export function LocationDetailPanel({
<TabsTrigger <TabsTrigger
key={tab.value} key={tab.value}
value={tab.value} value={tab.value}
className="rounded-none border-b-2 border-transparent data-[state=active]:border-orange-500 data-[state=active]:bg-orange-50 data-[state=active]:text-orange-700 px-4 py-2 text-sm whitespace-nowrap" className="rounded-none border-b-2 border-transparent data-[state=active]:border-gray-900 data-[state=active]:bg-gray-100 data-[state=active]:text-gray-900 data-[state=active]:font-semibold px-4 py-2 text-sm whitespace-nowrap"
> >
{tab.label} {tab.label}
</TabsTrigger> </TabsTrigger>
@@ -546,10 +546,10 @@ export function LocationDetailPanel({
return ( return (
<TabsContent key={tab.value} value={tab.value} className="flex-1 overflow-auto m-0 p-0"> <TabsContent key={tab.value} value={tab.value} className="flex-1 overflow-auto m-0 p-0">
<div className="bg-amber-50 border border-amber-200 rounded-lg m-3"> <div className="bg-gray-50 border border-gray-200 rounded-lg m-3">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="bg-amber-100/50"> <TableRow>
<TableHead className="font-semibold"></TableHead> <TableHead className="font-semibold"></TableHead>
<TableHead className="text-center font-semibold"></TableHead> <TableHead className="text-center font-semibold"></TableHead>
{isBendingTab && ( {isBendingTab && (
@@ -738,11 +738,10 @@ export function LocationDetailPanel({
</Table> </Table>
{/* 품목 추가 + 저장 버튼 */} {/* 품목 추가 + 저장 버튼 */}
<div className="p-3 flex items-center justify-between border-t border-amber-200"> <div className="p-3 flex items-center justify-between border-t border-gray-200">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
className="bg-blue-100 border-blue-300 text-blue-700 hover:bg-blue-200"
onClick={() => setItemSearchOpen(true)} onClick={() => setItemSearchOpen(true)}
disabled={disabled} disabled={disabled}
> >
@@ -751,7 +750,6 @@ export function LocationDetailPanel({
</Button> </Button>
<Button <Button
size="sm" size="sm"
className="bg-blue-600 hover:bg-blue-700 text-white"
onClick={onSaveItems} onClick={onSaveItems}
disabled={disabled} disabled={disabled}
> >

View File

@@ -312,7 +312,7 @@ export function LocationListPanel({
<div className="border-r border-gray-200 flex flex-col"> <div className="border-r border-gray-200 flex flex-col">
{/* ① 입력 영역 (상단으로 이동) */} {/* ① 입력 영역 (상단으로 이동) */}
{!disabled && ( {!disabled && (
<div className="bg-gray-50 p-4 space-y-3 border-b border-gray-200"> <div className="bg-gray-100 p-4 space-y-3 border-b border-gray-200 min-h-[128px]">
{/* 1행: 층, 부호, 가로, 세로, 제품코드, 수량 */} {/* 1행: 층, 부호, 가로, 세로, 제품코드, 수량 */}
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-2">
<div> <div>
@@ -322,7 +322,7 @@ export function LocationListPanel({
placeholder="예: 1F" placeholder="예: 1F"
value={formData.floor} value={formData.floor}
onChange={(e) => handleFormChange("floor", e.target.value)} onChange={(e) => handleFormChange("floor", e.target.value)}
className="h-8 text-sm placeholder:text-gray-300" className="h-8 text-sm border border-gray-300 placeholder:text-gray-400"
/> />
<datalist id="floorOptionList"> <datalist id="floorOptionList">
{FLOOR_OPTIONS.map((f) => ( {FLOOR_OPTIONS.map((f) => (
@@ -337,7 +337,7 @@ export function LocationListPanel({
placeholder="예: FSS-01" placeholder="예: FSS-01"
value={formData.code} value={formData.code}
onChange={(e) => handleFormChange("code", e.target.value)} onChange={(e) => handleFormChange("code", e.target.value)}
className="h-8 text-sm placeholder:text-gray-300" className="h-8 text-sm border border-gray-300 placeholder:text-gray-400"
/> />
<datalist id="locationCodeList"> <datalist id="locationCodeList">
{locationCodes.map((code) => ( {locationCodes.map((code) => (
@@ -351,7 +351,7 @@ export function LocationListPanel({
placeholder="예: 5000" placeholder="예: 5000"
value={formData.openWidth ? Number(formData.openWidth) : undefined} value={formData.openWidth ? Number(formData.openWidth) : undefined}
onChange={(value) => handleFormChange("openWidth", value?.toString() ?? "")} onChange={(value) => handleFormChange("openWidth", value?.toString() ?? "")}
className="h-8 text-sm placeholder:text-gray-300" className="h-8 text-sm border border-gray-300 placeholder:text-gray-400"
/> />
</div> </div>
<div> <div>
@@ -360,7 +360,7 @@ export function LocationListPanel({
placeholder="예: 3000" placeholder="예: 3000"
value={formData.openHeight ? Number(formData.openHeight) : undefined} value={formData.openHeight ? Number(formData.openHeight) : undefined}
onChange={(value) => handleFormChange("openHeight", value?.toString() ?? "")} onChange={(value) => handleFormChange("openHeight", value?.toString() ?? "")}
className="h-8 text-sm placeholder:text-gray-300" className="h-8 text-sm border border-gray-300 placeholder:text-gray-400"
/> />
</div> </div>
<div> <div>
@@ -376,14 +376,13 @@ export function LocationListPanel({
} }
}} }}
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{(() => { {(() => {
// 기존 개소가 있으면 동일 모델 제품만 표시 (예: KSS01 → KSS01 변형만)
const existingCode = locations.length > 0 ? locations[0].productCode : null; const existingCode = locations.length > 0 ? locations[0].productCode : null;
const existingModel = existingCode?.split('-')[1] ?? null; // FG-KSS01-... → KSS01 const existingModel = existingCode?.split('-')[1] ?? null;
const filtered = existingModel const filtered = existingModel
? finishedGoods.filter((fg) => fg.item_code.split('-')[1] === existingModel) ? finishedGoods.filter((fg) => fg.item_code.split('-')[1] === existingModel)
: finishedGoods; : finishedGoods;
@@ -401,14 +400,14 @@ export function LocationListPanel({
<QuantityInput <QuantityInput
value={formData.quantity ? Number(formData.quantity) : undefined} value={formData.quantity ? Number(formData.quantity) : undefined}
onChange={(value) => handleFormChange("quantity", value?.toString() ?? "")} onChange={(value) => handleFormChange("quantity", value?.toString() ?? "")}
className="h-8 text-sm" className="h-8 text-sm border border-gray-300"
min={1} min={1}
/> />
</div> </div>
</div> </div>
{/* 2행: 가이드레일, 전원, 제어기, 추가 버튼 */} {/* 2행: 가이드레일, 전원, 제어기, + 버튼 */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 items-end"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
<div> <div>
<label className="text-xs text-gray-600 flex items-center gap-1"> <label className="text-xs text-gray-600 flex items-center gap-1">
🔧 🔧
@@ -419,7 +418,7 @@ export function LocationListPanel({
onValueChange={(value) => handleFormChange("guideRailType", value)} onValueChange={(value) => handleFormChange("guideRailType", value)}
disabled disabled
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -439,7 +438,7 @@ export function LocationListPanel({
value={formData.motorPower} value={formData.motorPower}
onValueChange={(value) => handleFormChange("motorPower", value)} onValueChange={(value) => handleFormChange("motorPower", value)}
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -459,7 +458,7 @@ export function LocationListPanel({
value={formData.controller} value={formData.controller}
onValueChange={(value) => handleFormChange("controller", value)} onValueChange={(value) => handleFormChange("controller", value)}
> >
<SelectTrigger className="h-8 text-sm"> <SelectTrigger className="h-8 text-sm border border-gray-300">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@@ -471,21 +470,23 @@ export function LocationListPanel({
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<Button <div className="flex items-end">
onClick={handleAdd} <Button
className="h-8 bg-green-500 hover:bg-green-600 px-4" onClick={handleAdd}
> className="w-full h-[36px] bg-green-500 hover:bg-green-600"
<Plus className="h-4 w-4" /> >
</Button> <Plus className="h-4 w-4" />
</Button>
</div>
</div> </div>
</div> </div>
)} )}
{/* 발주 개소 목록 헤더 */} {/* 발주 개소 목록 헤더 */}
<div className="bg-blue-100 px-4 py-3 border-b border-blue-200"> <div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<h3 className="font-semibold text-blue-800"> <h3 className="font-semibold text-gray-700">
📋 ({locations.length}) ({locations.length})
</h3> </h3>
<div className="flex gap-1"> <div className="flex gap-1">
<Button <Button
@@ -527,13 +528,13 @@ export function LocationListPanel({
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow className="bg-gray-800 text-white"> <TableRow>
<TableHead className="text-center text-white"></TableHead> <TableHead className="text-center"></TableHead>
<TableHead className="text-center text-white"></TableHead> <TableHead className="text-center"></TableHead>
<TableHead className="text-center text-white"></TableHead> <TableHead className="text-center"></TableHead>
<TableHead className="text-center text-white"></TableHead> <TableHead className="text-center"></TableHead>
<TableHead className="text-center text-white"></TableHead> <TableHead className="text-center"></TableHead>
{!disabled && <TableHead className="w-[80px] text-center text-white"></TableHead>} {!disabled && <TableHead className="w-[80px] text-center"></TableHead>}
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@@ -548,7 +549,7 @@ export function LocationListPanel({
<TableRow <TableRow
key={loc.id} key={loc.id}
className={`cursor-pointer hover:bg-blue-50 ${ className={`cursor-pointer hover:bg-blue-50 ${
selectedLocationId === loc.id ? "bg-orange-100 border-l-4 border-l-orange-500" : "" selectedLocationId === loc.id ? "bg-gray-100 border-l-4 border-l-gray-800" : ""
}`} }`}
onClick={() => onSelectLocation(loc.id)} onClick={() => onSelectLocation(loc.id)}
> >

View File

@@ -8,10 +8,11 @@
"use client"; "use client";
import { Save, Check, ArrowLeft, Loader2, FileText, Pencil, ClipboardList, Percent, Calculator } from "lucide-react"; import { Save, Check, X, ArrowLeft, Loader2, FileText, Pencil, ClipboardList, Percent, Calculator } from "lucide-react";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { formatNumber } from "@/lib/utils/amount"; import { formatNumber } from "@/lib/utils/amount";
import { useMenuStore } from "@/stores/menuStore";
// ============================================================================= // =============================================================================
// Props // Props
@@ -29,8 +30,10 @@ interface QuoteFooterBarProps {
onSave: () => void; onSave: () => void;
/** 최종확정 */ /** 최종확정 */
onFinalize: () => void; onFinalize: () => void;
/** 뒤로가기 */ /** 뒤로가기 (목록으로) */
onBack: () => void; onBack: () => void;
/** 취소 (목록으로 이동) */
onCancel?: () => void;
/** 수정 */ /** 수정 */
onEdit?: () => void; onEdit?: () => void;
/** 수주등록 */ /** 수주등록 */
@@ -68,6 +71,7 @@ export function QuoteFooterBar({
onSave, onSave,
onFinalize, onFinalize,
onBack, onBack,
onCancel,
onEdit, onEdit,
onOrderRegister, onOrderRegister,
onOrderView, onOrderView,
@@ -81,28 +85,35 @@ export function QuoteFooterBar({
disabled: _disabled = false, disabled: _disabled = false,
isViewMode = false, isViewMode = false,
}: QuoteFooterBarProps) { }: QuoteFooterBarProps) {
return ( const sidebarCollapsed = useMenuStore((state) => state.sidebarCollapsed);
<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-3 py-3 md:px-6 md:py-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0">
{/* 왼쪽: 뒤로가기 + 금액 표시 */}
<div className="flex items-center gap-3 md:gap-6">
<Button
variant="outline"
onClick={onBack}
size="sm"
className="md:size-default shrink-0"
>
<ArrowLeft className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
<div className="min-w-0"> return (
<p className="text-xs md:text-sm text-gray-600 whitespace-nowrap"> </p> <div className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[24px] ${sidebarCollapsed ? 'md:left-[120px]' : 'md:left-[280px]'}`}>
<p className="text-lg md:text-3xl font-bold text-blue-600 whitespace-nowrap"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0">
{formatNumber(totalAmount)} {/* 왼쪽: 취소 + 목록으로 */}
<span className="text-sm md:text-lg font-normal text-gray-500 ml-1"></span> <div className="flex items-center gap-2">
</p> {!isViewMode && onCancel && (
</div> <Button
variant="outline"
onClick={onCancel}
size="sm"
className="md:size-default shrink-0"
>
<X className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
)}
{isViewMode && (
<Button
variant="outline"
onClick={onBack}
size="sm"
className="md:size-default shrink-0"
>
<ArrowLeft className="h-4 w-4 md:mr-2" />
<span className="hidden md:inline"></span>
</Button>
)}
</div> </div>
{/* 오른쪽: 버튼들 */} {/* 오른쪽: 버튼들 */}

View File

@@ -890,8 +890,8 @@ export function QuoteRegistration({
{/* 자동 견적 산출 섹션 */} {/* 자동 견적 산출 섹션 */}
<Card> <Card>
<CardHeader className="pb-3 bg-orange-50 border-b border-orange-200"> <CardHeader className="pb-3">
<CardTitle className="text-base font-semibold flex items-center gap-2 text-orange-800"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<Calculator className="h-5 w-5" /> <Calculator className="h-5 w-5" />
</CardTitle> </CardTitle>
@@ -979,6 +979,7 @@ export function QuoteRegistration({
locations={formData.locations} locations={formData.locations}
selectedLocationId={selectedLocationId} selectedLocationId={selectedLocationId}
onSelectLocation={setSelectedLocationId} onSelectLocation={setSelectedLocationId}
discountedTotalAmount={discountedTotalAmount}
/> />
</div> </div>
@@ -992,6 +993,7 @@ export function QuoteRegistration({
onSave={() => handleSave("temporary")} onSave={() => handleSave("temporary")}
onFinalize={() => handleSave("final")} onFinalize={() => handleSave("final")}
onBack={onBack} onBack={onBack}
onCancel={onBack}
onEdit={onEdit} onEdit={onEdit}
onOrderRegister={onOrderRegister} onOrderRegister={onOrderRegister}
onOrderView={onOrderView} onOrderView={onOrderView}

View File

@@ -44,6 +44,8 @@ interface QuoteSummaryPanelProps {
locations: LocationItem[]; locations: LocationItem[];
selectedLocationId: string | null; selectedLocationId: string | null;
onSelectLocation: (id: string) => void; onSelectLocation: (id: string) => void;
/** 할인 적용 후 총 금액 (할인이 있을 때만 표시) */
discountedTotalAmount?: number;
} }
// ============================================================================= // =============================================================================
@@ -54,6 +56,7 @@ export function QuoteSummaryPanel({
locations, locations,
selectedLocationId, selectedLocationId,
onSelectLocation, onSelectLocation,
discountedTotalAmount,
}: QuoteSummaryPanelProps) { }: QuoteSummaryPanelProps) {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 계산된 값 // 계산된 값
@@ -135,9 +138,9 @@ export function QuoteSummaryPanel({
return ( return (
<Card className="border-gray-200"> <Card className="border-gray-200">
<CardHeader className="pb-3 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-blue-200"> <CardHeader className="pb-3">
<CardTitle className="text-base font-semibold flex items-center gap-2"> <CardTitle className="text-base font-semibold flex items-center gap-2">
<Coins className="h-5 w-5 text-blue-600" /> <Coins className="h-5 w-5" />
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -147,7 +150,7 @@ export function QuoteSummaryPanel({
{/* 왼쪽: 개소별 합계 */} {/* 왼쪽: 개소별 합계 */}
<div className="p-4"> <div className="p-4">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<span className="text-blue-500">📍</span> <span>📍</span>
<h4 className="font-semibold text-gray-700"> </h4> <h4 className="font-semibold text-gray-700"> </h4>
</div> </div>
@@ -162,7 +165,7 @@ export function QuoteSummaryPanel({
key={loc.id} key={loc.id}
className={`flex items-center justify-between p-3 rounded-lg cursor-pointer transition-colors ${ className={`flex items-center justify-between p-3 rounded-lg cursor-pointer transition-colors ${
selectedLocationId === loc.id selectedLocationId === loc.id
? "bg-blue-100 border border-blue-300" ? "bg-gray-100 border border-gray-300"
: "bg-gray-50 hover:bg-gray-100 border border-transparent" : "bg-gray-50 hover:bg-gray-100 border border-transparent"
}`} }`}
onClick={() => onSelectLocation(loc.id)} onClick={() => onSelectLocation(loc.id)}
@@ -175,7 +178,7 @@ export function QuoteSummaryPanel({
</div> </div>
<div className="text-right"> <div className="text-right">
<p className="text-xs text-gray-500"></p> <p className="text-xs text-gray-500"></p>
<p className="font-bold text-blue-600"> <p className="font-bold text-gray-900">
{formatNumber(loc.totalPrice)} {formatNumber(loc.totalPrice)}
</p> </p>
{loc.unitPrice > 0 && ( {loc.unitPrice > 0 && (
@@ -193,7 +196,7 @@ export function QuoteSummaryPanel({
{/* 오른쪽: 상세별 합계 */} {/* 오른쪽: 상세별 합계 */}
<div className="p-4"> <div className="p-4">
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<span className="text-blue-500"></span> <span></span>
<h4 className="font-semibold text-gray-700"> <h4 className="font-semibold text-gray-700">
{selectedLocation && ( {selectedLocation && (
@@ -215,19 +218,19 @@ export function QuoteSummaryPanel({
) : ( ) : (
<div className="space-y-3"> <div className="space-y-3">
{detailTotals.map((category, index) => ( {detailTotals.map((category, index) => (
<div key={index} className="bg-blue-50 rounded-lg overflow-hidden border border-blue-200"> <div key={index} className="bg-gray-50 rounded-lg overflow-hidden border border-gray-200">
{/* 카테고리 헤더 */} {/* 카테고리 헤더 */}
<div className="flex items-center justify-between px-3 py-2 bg-blue-100/50"> <div className="flex items-center justify-between px-3 py-2 bg-gray-100">
<span className="font-semibold text-gray-700">{category.label}</span> <span className="font-semibold text-gray-700">{category.label}</span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs text-gray-500">({category.count})</span> <span className="text-xs text-gray-500">({category.count})</span>
<span className="font-bold text-blue-600"> <span className="font-bold text-gray-900">
{formatNumber(category.amount)} {formatNumber(category.amount)}
</span> </span>
</div> </div>
</div> </div>
{/* 품목 상세 목록 */} {/* 품목 상세 목록 */}
<div className="divide-y divide-blue-100"> <div className="divide-y divide-gray-100">
{category.items.map((item, itemIndex) => ( {category.items.map((item, itemIndex) => (
<div key={itemIndex} className="flex items-center justify-between px-3 py-2 bg-white"> <div key={itemIndex} className="flex items-center justify-between px-3 py-2 bg-white">
<div> <div>
@@ -236,7 +239,7 @@ export function QuoteSummaryPanel({
: {item.quantity} × : {formatNumber(item.unitPrice)} : {item.quantity} × : {formatNumber(item.unitPrice)}
</p> </p>
</div> </div>
<span className="text-sm font-medium text-blue-600"> <span className="text-sm font-medium text-gray-900">
{formatNumber(item.totalPrice)} {formatNumber(item.totalPrice)}
</span> </span>
</div> </div>
@@ -250,23 +253,32 @@ export function QuoteSummaryPanel({
</div> </div>
{/* 하단 바: 총 개소 수, 예상 견적금액, 견적 상태 */} {/* 하단 바: 총 개소 수, 예상 견적금액, 견적 상태 */}
<div className="bg-gray-900 text-white px-4 py-4 md:px-6 md:py-5 flex flex-wrap items-center justify-between gap-3"> <div className="bg-gray-50 border-t px-4 py-4 md:px-6 md:py-5 flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-4 md:gap-10"> <div className="flex items-center gap-4 md:gap-10">
<div> <div>
<p className="text-xs md:text-sm text-gray-400"> </p> <p className="text-xs md:text-sm text-muted-foreground"> </p>
<p className="text-2xl md:text-4xl font-bold">{locations.length}</p> <p className="text-2xl md:text-4xl font-bold">{locations.length}</p>
</div> </div>
<div> <div>
<p className="text-xs md:text-sm text-gray-400"> </p> <p className="text-xs md:text-sm text-muted-foreground"> </p>
<p className="text-2xl md:text-4xl font-bold text-blue-400"> <p className="text-2xl md:text-4xl font-bold">
{formatNumber(totalAmount)} {formatNumber(totalAmount)}
<span className="text-base md:text-xl ml-1"></span> <span className="text-base md:text-xl font-normal text-muted-foreground ml-1"></span>
</p> </p>
</div> </div>
{discountedTotalAmount !== undefined && discountedTotalAmount !== totalAmount && (
<div>
<p className="text-xs md:text-sm text-muted-foreground"> <span className="text-blue-500">( )</span></p>
<p className="text-2xl md:text-4xl font-bold text-blue-600">
{formatNumber(discountedTotalAmount)}
<span className="text-base md:text-xl font-normal text-muted-foreground ml-1"></span>
</p>
</div>
)}
</div> </div>
<div className="text-right"> <div className="text-right">
<p className="text-xs md:text-sm text-gray-400"> </p> <p className="text-xs md:text-sm text-muted-foreground"> </p>
<span className="inline-block bg-blue-500/20 text-blue-300 border border-blue-500/50 text-sm md:text-lg px-3 py-1 md:px-4 rounded"> <span className="inline-block bg-gray-200 text-gray-700 border border-gray-300 text-sm md:text-lg px-3 py-1 md:px-4 rounded">
</span> </span>
</div> </div>

View File

@@ -21,10 +21,7 @@ export const quoteConfig: DetailConfig = {
fields: [], // renderView/renderForm 사용으로 필드 정의 불필요 fields: [], // renderView/renderForm 사용으로 필드 정의 불필요
gridColumns: 2, gridColumns: 2,
actions: { actions: {
showBack: false, // QuoteFooterBar에서 처리 hideActions: true, // QuoteFooterBar에서 전체 처리
showDelete: false, // QuoteFooterBar에서 처리
showEdit: false, // QuoteFooterBar에서 처리
showSave: false, // QuoteFooterBar에서 처리
}, },
}; };
@@ -38,9 +35,7 @@ export const quoteCreateConfig: DetailConfig = {
basePath: '/sales/quote-management', basePath: '/sales/quote-management',
fields: [], fields: [],
actions: { actions: {
showBack: true, hideActions: true, // QuoteFooterBar에서 전체 처리
showSave: false, // QuoteFooterBar에서 처리
backLabel: '목록',
}, },
}; };
@@ -54,9 +49,7 @@ export const quoteEditConfig: DetailConfig = {
basePath: '/sales/quote-management', basePath: '/sales/quote-management',
fields: [], fields: [],
actions: { actions: {
showBack: true, hideActions: true, // QuoteFooterBar에서 전체 처리
showSave: false, // QuoteFooterBar에서 처리
backLabel: '목록',
}, },
}; };