summaryrefslogtreecommitdiff
path: root/lib/rfq-last
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-13 20:18:45 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-13 20:18:45 +0900
commitc882df5fa1a181d5bfd133d5980993ac43079b72 (patch)
tree2ed9a4c8787b52d589681b3a5ece5e940d3a5e74 /lib/rfq-last
parent7517ec0b016265a6c1d35f22ba6292c498669d43 (diff)
(김준회) 벤더 견적 응답: 통화 선택기 적용, 통화 가져오는 쿼리 명시적 JOIN으로 변경
Diffstat (limited to 'lib/rfq-last')
-rw-r--r--lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx68
-rw-r--r--lib/rfq-last/vendor-response/editor/quotation-items-table.tsx18
-rw-r--r--lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx16
3 files changed, 75 insertions, 27 deletions
diff --git a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx
index f479b48d..41592e46 100644
--- a/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx
+++ b/lib/rfq-last/vendor-response/editor/commercial-terms-form.tsx
@@ -6,11 +6,10 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Label } from "@/components/ui/label"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Alert, AlertDescription } from "@/components/ui/alert"
-import { InfoIcon, ChevronsUpDown, Check, CalendarIcon, Loader2 } from "lucide-react"
+import { InfoIcon, ChevronsUpDown, Check, CalendarIcon } from "lucide-react"
import { format } from "date-fns"
import { Calendar } from "@/components/ui/calendar"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
@@ -33,10 +32,13 @@ import {
} from "@/lib/procurement-select/service"
import { TAX_CONDITIONS } from "@/lib/tax-conditions/types"
import { toast } from "sonner"
+import { CurrencySelector } from "@/components/common/selectors/currency"
+import type { Currency } from "@/components/common/selectors/currency/currency-service"
interface CommercialTermsFormProps {
rfqDetail: any
rfq: any
+ onCurrencyDecimalPlacesChange?: (decimalPlaces: number) => void
}
interface SelectOption {
@@ -45,7 +47,7 @@ interface SelectOption {
description: string
}
-export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsFormProps) {
+export default function CommercialTermsForm({ rfqDetail, rfq, onCurrencyDecimalPlacesChange }: CommercialTermsFormProps) {
const { register, setValue, watch, formState: { errors } } = useFormContext()
console.log(rfqDetail,"rfqDetail")
@@ -71,6 +73,9 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF
const [shippingOpen, setShippingOpen] = React.useState(false)
const [destinationOpen, setDestinationOpen] = React.useState(false)
+ // 선택된 통화 상태
+ const [selectedCurrency, setSelectedCurrency] = React.useState<Currency | undefined>(undefined)
+
const vendorCurrency = watch("vendorCurrency")
const vendorPaymentTermsCode = watch("vendorPaymentTermsCode")
const vendorIncotermsCode = watch("vendorIncotermsCode")
@@ -82,6 +87,17 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF
// 구매자 제시 조건과 다른지 확인
const isDifferentCurrency = vendorCurrency !== rfqDetail.currency
+
+ // 통화 선택 핸들러
+ const handleCurrencySelect = React.useCallback((currency: Currency) => {
+ setSelectedCurrency(currency)
+ setValue("vendorCurrency", currency.CURRENCY_CODE)
+
+ // 소수점 자리수 변경 알림
+ if (onCurrencyDecimalPlacesChange) {
+ onCurrencyDecimalPlacesChange(currency.DECIMAL_PLACES)
+ }
+ }, [setValue, onCurrencyDecimalPlacesChange])
const isDifferentPaymentTerms = vendorPaymentTermsCode !== rfqDetail.paymentTermsCode
const isDifferentIncoterms = vendorIncotermsCode !== rfqDetail.incotermsCode
@@ -152,6 +168,32 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF
}
}, [])
+ // 초기 통화 로드 (기존 선택된 통화가 있는 경우)
+ React.useEffect(() => {
+ const loadInitialCurrency = async () => {
+ if (vendorCurrency && !selectedCurrency) {
+ try {
+ const { getCurrencies } = await import("@/components/common/selectors/currency/currency-service")
+ const result = await getCurrencies()
+
+ if (result.success) {
+ const currency = result.data.find(c => c.CURRENCY_CODE === vendorCurrency)
+ if (currency) {
+ setSelectedCurrency(currency)
+ if (onCurrencyDecimalPlacesChange) {
+ onCurrencyDecimalPlacesChange(currency.DECIMAL_PLACES)
+ }
+ }
+ }
+ } catch (error) {
+ console.error("Failed to load initial currency:", error)
+ }
+ }
+ }
+
+ loadInitialCurrency()
+ }, [vendorCurrency, selectedCurrency, onCurrencyDecimalPlacesChange])
+
// 컴포넌트 마운트 시 데이터 로드
React.useEffect(() => {
loadIncoterms()
@@ -185,21 +227,11 @@ export default function CommercialTermsForm({ rfqDetail, rfq }: CommercialTermsF
</div>
<div className="space-y-2">
<Label htmlFor="vendorCurrency">벤더 제안 통화</Label>
- <Select
- value={vendorCurrency}
- onValueChange={(value) => setValue("vendorCurrency", value)}
- >
- <SelectTrigger>
- <SelectValue placeholder="통화 선택" />
- </SelectTrigger>
- <SelectContent>
- <SelectItem value="USD">USD</SelectItem>
- <SelectItem value="KRW">KRW</SelectItem>
- <SelectItem value="EUR">EUR</SelectItem>
- <SelectItem value="JPY">JPY</SelectItem>
- <SelectItem value="CNY">CNY</SelectItem>
- </SelectContent>
- </Select>
+ <CurrencySelector
+ selectedCurrency={selectedCurrency}
+ onCurrencySelect={handleCurrencySelect}
+ placeholder="통화를 선택하세요"
+ />
</div>
</div>
{isDifferentCurrency && (
diff --git a/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx b/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx
index 9f2af046..266ba39b 100644
--- a/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx
+++ b/lib/rfq-last/vendor-response/editor/quotation-items-table.tsx
@@ -32,9 +32,10 @@ import {
interface QuotationItemsTableProps {
prItems: any[]
+ decimalPlaces?: number
}
-export default function QuotationItemsTable({ prItems }: QuotationItemsTableProps) {
+export default function QuotationItemsTable({ prItems, decimalPlaces = 2 }: QuotationItemsTableProps) {
const { control, register, setValue, watch } = useFormContext()
const { fields } = useFieldArray({
control,
@@ -328,7 +329,10 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp
const unitPriceValue = row.getCell(unitPriceCol).value
const unitPrice = typeof unitPriceValue === 'number' ? unitPriceValue : parseFloat(String(unitPriceValue || 0))
if (!isNaN(unitPrice) && unitPrice >= 0) {
- setValue(`quotationItems.${index}.unitPrice`, Math.floor(unitPrice))
+ const formattedPrice = decimalPlaces === 0
+ ? Math.floor(unitPrice)
+ : parseFloat(unitPrice.toFixed(decimalPlaces))
+ setValue(`quotationItems.${index}.unitPrice`, formattedPrice)
calculateTotal(index)
successCount++
}
@@ -782,15 +786,19 @@ export default function QuotationItemsTable({ prItems }: QuotationItemsTableProp
<Input
type="number"
min="0"
- step="1"
+ step={decimalPlaces === 0 ? "1" : `0.${"1".padStart(decimalPlaces, "0")}`}
{...register(`quotationItems.${index}.unitPrice`, { valueAsNumber: true })}
onChange={(e) => {
- const value = Math.max(0, Math.floor(parseFloat(e.target.value) || 0))
+ const inputValue = parseFloat(e.target.value) || 0
+ const value = Math.max(0, decimalPlaces === 0
+ ? Math.floor(inputValue)
+ : parseFloat(inputValue.toFixed(decimalPlaces))
+ )
setValue(`quotationItems.${index}.unitPrice`, value)
calculateTotal(index)
}}
className="w-[120px]"
- placeholder="0"
+ placeholder={decimalPlaces === 0 ? "0" : `0.${"0".repeat(decimalPlaces)}`}
/>
<span className="text-xs text-muted-foreground">
{currency}
diff --git a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx
index 4c14a7f3..b7e67881 100644
--- a/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx
+++ b/lib/rfq-last/vendor-response/editor/vendor-response-editor.tsx
@@ -20,7 +20,7 @@ interface FileWithType extends File {
description?: string
}
import { formatDate, formatCurrency } from "@/lib/utils"
-import { Shield, FileText, CheckCircle, XCircle, Clock, Download, Eye, Save, Send, AlertCircle, Upload, } from "lucide-react"
+import { Shield, FileText, CheckCircle, Clock, Save, Send, AlertCircle, Upload, } from "lucide-react"
import { Progress } from "@/components/ui/progress"
import { Alert, AlertDescription } from "@/components/ui/alert"
@@ -129,6 +129,7 @@ export default function VendorResponseEditor({
const [existingAttachments, setExistingAttachments] = useState<any[]>([])
const [deletedAttachments, setDeletedAttachments] = useState<any[]>([])
const [uploadProgress, setUploadProgress] = useState(0) // 추가
+ const [currencyDecimalPlaces, setCurrencyDecimalPlaces] = useState<number>(2) // 통화별 소수점 자리수
console.log(existingResponse,"existingResponse")
@@ -520,7 +521,7 @@ export default function VendorResponseEditor({
{contract.templateName}
</h4>
<Badge
- variant={contract.signedAt ? "success" : "warning"}
+ variant={contract.signedAt ? "success" : "secondary"}
className="text-xs mt-1.5"
>
{contract.signedAt ? (
@@ -592,11 +593,18 @@ export default function VendorResponseEditor({
</TabsContent>
<TabsContent value="terms" className="mt-6">
- <CommercialTermsForm rfqDetail={rfqDetail} rfq={rfq} />
+ <CommercialTermsForm
+ rfqDetail={rfqDetail}
+ rfq={rfq}
+ onCurrencyDecimalPlacesChange={setCurrencyDecimalPlaces}
+ />
</TabsContent>
<TabsContent value="items" className="mt-6">
- <QuotationItemsTable prItems={prItems} />
+ <QuotationItemsTable
+ prItems={prItems}
+ decimalPlaces={currencyDecimalPlaces}
+ />
</TabsContent>
<TabsContent value="attachments" className="mt-6">