diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-13 20:18:45 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-13 20:18:45 +0900 |
| commit | c882df5fa1a181d5bfd133d5980993ac43079b72 (patch) | |
| tree | 2ed9a4c8787b52d589681b3a5ece5e940d3a5e74 /lib/rfq-last | |
| parent | 7517ec0b016265a6c1d35f22ba6292c498669d43 (diff) | |
(김준회) 벤더 견적 응답: 통화 선택기 적용, 통화 가져오는 쿼리 명시적 JOIN으로 변경
Diffstat (limited to 'lib/rfq-last')
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"> |
