summaryrefslogtreecommitdiff
path: root/lib/rfq-last/vendor
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-09-24 07:59:35 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-09-24 07:59:35 +0000
commit4fe733d7d9d3d873fa395133e9a42cf9fc8c44dc (patch)
treecd2429665d9fd55620c748c41d600f3e2cb0e685 /lib/rfq-last/vendor
parentee52c983423fbc63373ce1dacb041d973da502df (diff)
(최겸) 구매 피드백 반영(용어 수정, 견적유형, 조건설정, 첨부파일 선택사항 등)
Diffstat (limited to 'lib/rfq-last/vendor')
-rw-r--r--lib/rfq-last/vendor/batch-update-conditions-dialog.tsx59
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx118
-rw-r--r--lib/rfq-last/vendor/send-rfq-dialog.tsx9
-rw-r--r--lib/rfq-last/vendor/vendor-detail-dialog.tsx4
4 files changed, 137 insertions, 53 deletions
diff --git a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx
index af38ff45..893fd9a3 100644
--- a/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx
+++ b/lib/rfq-last/vendor/batch-update-conditions-dialog.tsx
@@ -128,6 +128,7 @@ export function BatchUpdateConditionsDialog({
const [shippingOpen, setShippingOpen] = React.useState(false);
const [destinationOpen, setDestinationOpen] = React.useState(false);
const [calendarOpen, setCalendarOpen] = React.useState(false);
+ const [currencyOpen, setCurrencyOpen] = React.useState(false);
// 체크박스로 각 필드 업데이트 여부 관리
const [fieldsToUpdate, setFieldsToUpdate] = React.useState({
@@ -381,7 +382,28 @@ export function BatchUpdateConditionsDialog({
{/* 기본 조건 설정 */}
<Card>
<CardHeader>
- <CardTitle className="text-lg">기본 조건</CardTitle>
+ <div className="flex items-center justify-between">
+ <CardTitle className="text-lg">기본 조건</CardTitle>
+ <Button
+ type="button"
+ variant="outline"
+ size="sm"
+ onClick={() => {
+ // 기본조건만 전체 선택/해제
+ const basicFields = ['currency', 'paymentTermsCode', 'incoterms', 'deliveryDate', 'contractDuration', 'taxCode', 'shipping'];
+ const allBasicSelected = basicFields.every(field => fieldsToUpdate[field as keyof typeof fieldsToUpdate]);
+ const newState = { ...fieldsToUpdate };
+
+ basicFields.forEach(field => {
+ newState[field as keyof typeof newState] = !allBasicSelected;
+ });
+
+ setFieldsToUpdate(newState);
+ }}
+ >
+ {['currency', 'paymentTermsCode', 'incoterms', 'deliveryDate', 'contractDuration', 'taxCode', 'shipping'].every(field => fieldsToUpdate[field as keyof typeof fieldsToUpdate]) ? '전체 해제' : '전체 선택'}
+ </Button>
+ </div>
</CardHeader>
<CardContent className="space-y-4">
{/* 통화 */}
@@ -405,11 +427,12 @@ export function BatchUpdateConditionsDialog({
</FormLabel>
<div className="col-span-2">
<FormControl>
- <Popover>
+ <Popover open={currencyOpen} onOpenChange={setCurrencyOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
+ aria-expanded={currencyOpen}
className="w-full justify-between"
disabled={!fieldsToUpdate.currency}
>
@@ -433,7 +456,10 @@ export function BatchUpdateConditionsDialog({
<CommandItem
key={currency}
value={currency}
- onSelect={() => field.onChange(currency)}
+ onSelect={() => {
+ field.onChange(currency);
+ setCurrencyOpen(false);
+ }}
>
{currency}
<Check
@@ -1017,9 +1043,12 @@ export function BatchUpdateConditionsDialog({
<div className="flex items-center gap-4">
<Checkbox
checked={fieldsToUpdate.materialPrice}
- onCheckedChange={(checked) =>
- setFieldsToUpdate({ ...fieldsToUpdate, materialPrice: !!checked })
- }
+ onCheckedChange={(checked) => {
+ setFieldsToUpdate({ ...fieldsToUpdate, materialPrice: !!checked });
+ if (checked) {
+ form.setValue("materialPriceRelatedYn", true);
+ }
+ }}
/>
<FormField
control={form.control}
@@ -1053,9 +1082,12 @@ export function BatchUpdateConditionsDialog({
<div className="flex items-center gap-4">
<Checkbox
checked={fieldsToUpdate.sparepart}
- onCheckedChange={(checked) =>
- setFieldsToUpdate({ ...fieldsToUpdate, sparepart: !!checked })
- }
+ onCheckedChange={(checked) => {
+ setFieldsToUpdate({ ...fieldsToUpdate, sparepart: !!checked });
+ if (checked) {
+ form.setValue("sparepartYn", true);
+ }
+ }}
/>
<FormField
control={form.control}
@@ -1108,9 +1140,12 @@ export function BatchUpdateConditionsDialog({
<div className="flex items-center gap-4">
<Checkbox
checked={fieldsToUpdate.first}
- onCheckedChange={(checked) =>
- setFieldsToUpdate({ ...fieldsToUpdate, first: !!checked })
- }
+ onCheckedChange={(checked) => {
+ setFieldsToUpdate({ ...fieldsToUpdate, first: !!checked });
+ if (checked) {
+ form.setValue("firstYn", true);
+ }
+ }}
/>
<FormField
control={form.control}
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx
index ad89d1dc..98d53f5d 100644
--- a/lib/rfq-last/vendor/rfq-vendor-table.tsx
+++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx
@@ -488,6 +488,37 @@ export function RfqVendorTable({
}
}, [rfqId, rfqCode, router]);
+ // vendor status에 따른 category 분류 함수
+ const getVendorCategoryFromStatus = React.useCallback((status: string | null): string => {
+ if (!status) return "미분류";
+
+ const categoryMap: Record<string, string> = {
+ "PENDING_REVIEW": "발굴업체", // 가입 신청 중
+ "IN_REVIEW": "잠재업체", // 심사 중
+ "REJECTED": "발굴업체", // 심사 거부됨
+ "IN_PQ": "잠재업체", // PQ 진행 중
+ "PQ_SUBMITTED": "잠재업체", // PQ 제출
+ "PQ_FAILED": "잠재업체", // PQ 실패
+ "PQ_APPROVED": "잠재업체", // PQ 통과
+ "APPROVED": "잠재업체", // 승인됨
+ "READY_TO_SEND": "잠재업체", // 정규등록검토
+ "ACTIVE": "정규업체", // 활성 상태 (실제 거래 중)
+ "INACTIVE": "중지업체", // 비활성 상태
+ "BLACKLISTED": "중지업체", // 거래 금지
+ };
+
+ return categoryMap[status] || "미분류";
+ }, []);
+
+ // vendorCountry를 보기 좋은 형태로 변환
+ const formatVendorCountry = React.useCallback((country: string | null): string => {
+ if (!country) return "미지정";
+
+ // KR 또는 한국이면 내자, 그 외는 전부 외자
+ const isLocal = country === "KR" || country === "한국";
+ return isLocal ? `내자(${country})` : `외자(${country})`;
+ }, []);
+
// 액션 처리
const handleAction = React.useCallback(async (action: string, vendor: any) => {
switch (action) {
@@ -636,7 +667,15 @@ export function RfqVendorTable({
header: ({ column }) => <ClientDataTableColumnHeaderSimple column={column} title="업체분류" />,
filterFn: createFilterFn("text"),
- cell: ({ row }) => row.original.vendorCategory || "-",
+ cell: ({ row }) => {
+ const status = row.original.status;
+ const category = getVendorCategoryFromStatus(status);
+ return (
+ <Badge variant="outline" className="text-xs">
+ {category}
+ </Badge>
+ );
+ },
size: 100,
},
{
@@ -646,10 +685,11 @@ export function RfqVendorTable({
cell: ({ row }) => {
const country = row.original.vendorCountry;
+ const formattedCountry = formatVendorCountry(country);
const isLocal = country === "KR" || country === "한국";
return (
<Badge variant={isLocal ? "default" : "secondary"}>
- {country || "-"}
+ {formattedCountry}
</Badge>
);
},
@@ -1503,6 +1543,37 @@ export function RfqVendorTable({
{selectedRows.length > 0 && (
<>
+ {/* 정보 일괄 입력 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => setIsBatchUpdateOpen(true)}
+ disabled={isLoadingSendData}
+ >
+ <Settings2 className="h-4 w-4 mr-2" />
+ 정보 일괄 입력 ({selectedRows.length})
+ </Button>
+
+ {/* RFQ 발송 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleBulkSend}
+ disabled={isLoadingSendData || selectedRows.length === 0}
+ >
+ {isLoadingSendData ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ 데이터 준비중...
+ </>
+ ) : (
+ <>
+ <Send className="h-4 w-4 mr-2" />
+ RFQ 발송 ({shortListCount})
+ </>
+ )}
+ </Button>
+
{/* Short List 확정 버튼 */}
{rfqCode?.startsWith("I") &&
<Button
@@ -1539,37 +1610,6 @@ export function RfqVendorTable({
견적 비교
{quotationCount > 0 && ` (${quotationCount})`}
</Button>
-
- {/* 정보 일괄 입력 버튼 */}
- <Button
- variant="outline"
- size="sm"
- onClick={() => setIsBatchUpdateOpen(true)}
- disabled={isLoadingSendData}
- >
- <Settings2 className="h-4 w-4 mr-2" />
- 정보 일괄 입력 ({selectedRows.length})
- </Button>
-
- {/* RFQ 발송 버튼 */}
- <Button
- variant="outline"
- size="sm"
- onClick={handleBulkSend}
- disabled={isLoadingSendData || selectedRows.length === 0}
- >
- {isLoadingSendData ? (
- <>
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
- 데이터 준비중...
- </>
- ) : (
- <>
- <Send className="h-4 w-4 mr-2" />
- RFQ 발송 ({shortListCount})
- </>
- )}
- </Button>
</>
)}
@@ -1679,6 +1719,18 @@ export function RfqVendorTable({
}}
/>
)}
+
+ {/* AVL 벤더 연동 다이얼로그 */}
+ <AvlVendorDialog
+ open={isAvlDialogOpen}
+ onOpenChange={setIsAvlDialogOpen}
+ rfqId={rfqId}
+ rfqCode={rfqCode}
+ onSuccess={() => {
+ setIsAvlDialogOpen(false);
+ router.refresh();
+ }}
+ />
</>
);
} \ No newline at end of file
diff --git a/lib/rfq-last/vendor/send-rfq-dialog.tsx b/lib/rfq-last/vendor/send-rfq-dialog.tsx
index 34777864..ed43d87f 100644
--- a/lib/rfq-last/vendor/send-rfq-dialog.tsx
+++ b/lib/rfq-last/vendor/send-rfq-dialog.tsx
@@ -653,10 +653,7 @@ export function SendRfqDialog({
return;
}
- if (selectedAttachments.length === 0) {
- toast.warning("최소 하나 이상의 첨부파일을 선택해주세요.");
- return;
- }
+ // 첨부파일은 선택사항 - 없어도 발송 가능
// 재발송 업체 확인
const resendVendors = vendorsWithRecipients.filter(v => v.sendVersion && v.sendVersion > 0);
@@ -1350,12 +1347,12 @@ export function SendRfqDialog({
</Button>
<Button
onClick={handleSend}
- disabled={isSending || isGeneratingPdfs || selectedAttachments.length === 0}
+ disabled={isSending || isGeneratingPdfs}
>
{isGeneratingPdfs ? (
<>
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
- 계약서 생성중... ({Math.round(pdfGenerationProgress)}%)
+ RFQ 송부중... ({Math.round(pdfGenerationProgress)}%)
</>
) : isSending ? (
<>
diff --git a/lib/rfq-last/vendor/vendor-detail-dialog.tsx b/lib/rfq-last/vendor/vendor-detail-dialog.tsx
index e4c78656..54aada1d 100644
--- a/lib/rfq-last/vendor/vendor-detail-dialog.tsx
+++ b/lib/rfq-last/vendor/vendor-detail-dialog.tsx
@@ -253,8 +253,8 @@ export function VendorResponseDetailDialog({
<div className="flex items-center gap-2">
<Globe className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">국가</span>
- <Badge variant={data.vendorCountry === "KR" ? "default" : "secondary"} className="ml-auto">
- {data.vendorCountry}
+ <Badge variant={data.vendorCountry === "KR" || data.vendorCountry === "한국" ? "default" : "secondary"} className="ml-auto">
+ {data.vendorCountry === "KR" || data.vendorCountry === "한국" ? `내자(${data.vendorCountry})` : `외자(${data.vendorCountry})`}
</Badge>
</div>
</div>