summaryrefslogtreecommitdiff
path: root/lib/rfq-last
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-10-15 12:52:11 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-10-15 12:52:11 +0000
commitb54f6f03150dd78d86db62201b6386bf14b72394 (patch)
treeb3092bb34805fdc65eee5282e86a9fb90ba20d6e /lib/rfq-last
parentc1bd1a2f499ee2f0742170021b37dab410983ab7 (diff)
(대표님) 커버, 데이터룸, 파일매니저, 담당자할당 등
Diffstat (limited to 'lib/rfq-last')
-rw-r--r--lib/rfq-last/service.ts1
-rw-r--r--lib/rfq-last/table/rfq-assign-pic-dialog.tsx36
-rw-r--r--lib/rfq-last/table/rfq-table-toolbar-actions.tsx28
-rw-r--r--lib/rfq-last/vendor/rfq-vendor-table.tsx32
4 files changed, 58 insertions, 39 deletions
diff --git a/lib/rfq-last/service.ts b/lib/rfq-last/service.ts
index d4efb81d..8475aac0 100644
--- a/lib/rfq-last/service.ts
+++ b/lib/rfq-last/service.ts
@@ -100,6 +100,7 @@ export async function getRfqs(input: GetRfqsSchema) {
ilike(rfqsLastView.packageNo, s),
ilike(rfqsLastView.packageName, s),
ilike(rfqsLastView.picName, s),
+ ilike(rfqsLastView.picCode, s),
ilike(rfqsLastView.engPicName, s),
ilike(rfqsLastView.projectCode, s),
ilike(rfqsLastView.projectName, s),
diff --git a/lib/rfq-last/table/rfq-assign-pic-dialog.tsx b/lib/rfq-last/table/rfq-assign-pic-dialog.tsx
index 9ca34ccd..70d2ed7e 100644
--- a/lib/rfq-last/table/rfq-assign-pic-dialog.tsx
+++ b/lib/rfq-last/table/rfq-assign-pic-dialog.tsx
@@ -39,15 +39,6 @@ export function RfqAssignPicDialog({
const [selectedCode, setSelectedCode] = React.useState<PurchaseGroupCodeWithUser | undefined>(undefined);
const [selectorOpen, setSelectorOpen] = React.useState(false);
- // ITB만 필터링 (rfqCode가 "I"로 시작하는 것)
- const itbCodes = React.useMemo(() => {
- return selectedRfqCodes.filter(code => code.startsWith("I"));
- }, [selectedRfqCodes]);
-
- const itbIds = React.useMemo(() => {
- return selectedRfqIds.filter((id, index) => selectedRfqCodes[index]?.startsWith("I"));
- }, [selectedRfqIds, selectedRfqCodes]);
-
// 다이얼로그 열릴 때 초기화
React.useEffect(() => {
if (open) {
@@ -81,15 +72,15 @@ export function RfqAssignPicDialog({
return;
}
- if (itbIds.length === 0) {
- toast.error("선택한 항목 중 ITB가 없습니다");
+ if (selectedRfqIds.length === 0) {
+ toast.error("담당자 지정이 가능한 ITB가 없습니다");
return;
}
setIsAssigning(true);
try {
const result = await assignPicToRfqs({
- rfqIds: itbIds,
+ rfqIds: selectedRfqIds,
picUserId: selectedCode.user.id,
});
@@ -127,32 +118,27 @@ export function RfqAssignPicDialog({
<label className="text-sm font-medium">선택된 ITB</label>
<div className="p-3 bg-muted rounded-md">
<div className="flex items-center gap-2 mb-2">
- <Badge variant="secondary">{itbCodes.length}건</Badge>
- {itbCodes.length !== selectedRfqCodes.length && (
- <span className="text-xs text-muted-foreground">
- (전체 {selectedRfqCodes.length}건 중)
- </span>
- )}
+ <Badge variant="secondary">{selectedRfqCodes.length}건</Badge>
</div>
<div className="max-h-[100px] overflow-y-auto">
<div className="flex flex-wrap gap-1">
- {itbCodes.slice(0, 10).map((code, index) => (
+ {selectedRfqCodes.slice(0, 10).map((code, index) => (
<Badge key={index} variant="outline" className="text-xs">
{code}
</Badge>
))}
- {itbCodes.length > 10 && (
+ {selectedRfqCodes.length > 10 && (
<Badge variant="outline" className="text-xs">
- +{itbCodes.length - 10}개
+ +{selectedRfqCodes.length - 10}개
</Badge>
)}
</div>
</div>
</div>
- {itbCodes.length === 0 && (
+ {selectedRfqCodes.length === 0 && (
<Alert className="border-orange-200 bg-orange-50">
<AlertDescription className="text-orange-800">
- 선택한 항목 중 ITB (I로 시작하는 코드)가 없습니다.
+ 담당자 지정이 가능한 ITB가 없습니다. (상태가 "RFQ 생성" 또는 "구매담당지정"인 ITB만 가능)
</AlertDescription>
</Alert>
)}
@@ -165,7 +151,7 @@ export function RfqAssignPicDialog({
type="button"
variant="outline"
className="w-full justify-start h-auto min-h-[40px]"
- disabled={itbCodes.length === 0}
+ disabled={selectedRfqCodes.length === 0}
onClick={() => setSelectorOpen(true)}
>
{selectedCode ? (
@@ -227,7 +213,7 @@ export function RfqAssignPicDialog({
<Button
type="submit"
onClick={handleAssign}
- disabled={!selectedCode || !selectedCode.user || itbCodes.length === 0 || isAssigning}
+ disabled={!selectedCode || !selectedCode.user || selectedRfqCodes.length === 0 || isAssigning}
>
{isAssigning ? (
<>
diff --git a/lib/rfq-last/table/rfq-table-toolbar-actions.tsx b/lib/rfq-last/table/rfq-table-toolbar-actions.tsx
index 7d48f5a4..00c41402 100644
--- a/lib/rfq-last/table/rfq-table-toolbar-actions.tsx
+++ b/lib/rfq-last/table/rfq-table-toolbar-actions.tsx
@@ -36,17 +36,27 @@ export function RfqTableToolbarActions<TData>({
// 선택된 RFQ의 ID와 코드 추출
const selectedRfqData = React.useMemo(() => {
const rows = selectedRows.map(row => row.original as RfqsLastView);
+ const assignableRows = rows.filter(row =>
+ row.rfqCode?.startsWith("I") &&
+ (row.status === "RFQ 생성" || row.status === "구매담당지정")
+ );
+
return {
ids: rows.map(row => row.id),
codes: rows.map(row => row.rfqCode || ""),
+ statuses: rows.map(row => row.status || ""),
// "I"로 시작하는 ITB만 필터링
itbCount: rows.filter(row => row.rfqCode?.startsWith("I")).length,
- totalCount: rows.length
+ totalCount: rows.length,
+ // 담당자 지정 가능한 ITB (상태가 "RFQ 생성" 또는 "구매담당지정"인 ITB)
+ assignableItbCount: assignableRows.length,
+ assignableIds: assignableRows.map(row => row.id),
+ assignableCodes: assignableRows.map(row => row.rfqCode || "")
};
}, [selectedRows]);
- // 담당자 지정 가능 여부 체크 ("I"로 시작하는 항목이 있는지)
- const canAssignPic = selectedRfqData.itbCount > 0;
+ // 담당자 지정 가능 여부 체크 (상태가 "RFQ 생성" 또는 "구매담당지정"인 ITB가 있는지)
+ const canAssignPic = selectedRfqData.assignableItbCount > 0;
const handleAssignSuccess = () => {
// 테이블 선택 초기화
@@ -76,15 +86,15 @@ export function RfqTableToolbarActions<TData>({
<Users className="h-4 w-4" />
담당자 지정
<Badge variant="secondary" className="ml-1">
- {selectedRfqData.itbCount}건
+ {selectedRfqData.assignableItbCount}건
</Badge>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>선택한 ITB에 구매 담당자를 지정합니다</p>
- {selectedRfqData.itbCount !== selectedRfqData.totalCount && (
+ {selectedRfqData.assignableItbCount !== selectedRfqData.itbCount && (
<p className="text-xs text-muted-foreground mt-1">
- 전체 {selectedRfqData.totalCount}건 중 ITB {selectedRfqData.itbCount}건만 지정됩니다
+ 전체 ITB {selectedRfqData.itbCount}건 중 {selectedRfqData.assignableItbCount}건만 지정 가능합니다
</p>
)}
</TooltipContent>
@@ -103,7 +113,7 @@ export function RfqTableToolbarActions<TData>({
</Badge>
{selectedRfqData.totalCount !== selectedRfqData.itbCount && (
<Badge variant="outline" className="text-xs">
- ITB {selectedRfqData.itbCount}건
+ ITB {selectedRfqData.itbCount}건 (지정가능 {selectedRfqData.assignableItbCount}건)
</Badge>
)}
</div>
@@ -139,8 +149,8 @@ export function RfqTableToolbarActions<TData>({
<RfqAssignPicDialog
open={showAssignDialog}
onOpenChange={setShowAssignDialog}
- selectedRfqIds={selectedRfqData.ids}
- selectedRfqCodes={selectedRfqData.codes}
+ selectedRfqIds={selectedRfqData.assignableIds}
+ selectedRfqCodes={selectedRfqData.assignableCodes}
onSuccess={handleAssignSuccess}
/>
</>
diff --git a/lib/rfq-last/vendor/rfq-vendor-table.tsx b/lib/rfq-last/vendor/rfq-vendor-table.tsx
index dc5564e2..428160d5 100644
--- a/lib/rfq-last/vendor/rfq-vendor-table.tsx
+++ b/lib/rfq-last/vendor/rfq-vendor-table.tsx
@@ -753,8 +753,10 @@ export function RfqVendorTable({
filterFn: createFilterFn("text"),
cell: ({ row }) => {
- const status = row.original.tbeStatus;
+ const status = row.original.tbeStatus?.trim();
+ const rfqCode = row.original.rfqCode?.trim();
+ // 생성중/준비중은 대기 표시(비클릭)
if (!status || status === "준비중") {
return (
<Badge variant="outline" className="text-gray-500">
@@ -772,8 +774,28 @@ export function RfqVendorTable({
"취소": { variant: "destructive", icon: <XCircle className="h-3 w-3 mr-1" /> },
}[status] || { variant: "outline", icon: null, color: "text-gray-600" };
+ const isClickable = !!rfqCode;
+
return (
- <Badge variant={statusConfig.variant as any} className={statusConfig.color}>
+ <Badge
+ role={isClickable ? "button" : undefined}
+ tabIndex={isClickable ? 0 : -1}
+ variant={statusConfig.variant as any}
+ className={cn(statusConfig.color, isClickable && "cursor-pointer hover:underline")}
+ onClick={(e) => {
+ if (!isClickable) return;
+ e.stopPropagation();
+ e.preventDefault();
+ router.push(`/evcp/tbe-last?search=${encodeURIComponent(rfqCode!)}`);
+ // window.open(
+ // `/evcp/tbe-last?search=${encodeURIComponent(rfqCode!)}`,
+ // "_blank",
+ // "noopener,noreferrer"
+ // );
+ // 새 창으로 이동
+ }}
+ title={isClickable ? `TBE로 이동: ${rfqCode}` : undefined}
+ >
{statusConfig.icon}
{status}
</Badge>
@@ -802,19 +824,19 @@ export function RfqVendorTable({
"Acceptable": {
variant: "success",
icon: <CheckCircle className="h-3 w-3" />,
- text: "적합",
+ text: "Acceptable",
color: "bg-green-50 text-green-700 border-green-200"
},
"Acceptable with Comment": {
variant: "warning",
icon: <AlertCircle className="h-3 w-3" />,
- text: "조건부 적합",
+ text: "Acceptable with Comment",
color: "bg-yellow-50 text-yellow-700 border-yellow-200"
},
"Not Acceptable": {
variant: "destructive",
icon: <XCircle className="h-3 w-3" />,
- text: "부적합",
+ text: "Not Acceptable",
color: "bg-red-50 text-red-700 border-red-200"
},
}[result];