diff options
| -rw-r--r-- | lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx | 62 | ||||
| -rw-r--r-- | lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx | 69 | ||||
| -rw-r--r-- | lib/pq/service.ts | 47 |
3 files changed, 172 insertions, 6 deletions
diff --git a/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx b/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx index 94b33ab4..e135d8b8 100644 --- a/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx +++ b/lib/pq/pq-review-table-new/cancel-investigation-dialog.tsx @@ -55,9 +55,9 @@ export function CancelInvestigationDialog({ >
취소
</Button>
- <Button
- variant="destructive"
- onClick={handleConfirm}
+ <Button
+ variant="destructive"
+ onClick={handleConfirm}
disabled={isPending}
>
{isPending ? "처리 중..." : "실사 의뢰 취소"}
@@ -66,4 +66,60 @@ export function CancelInvestigationDialog({ </DialogContent>
</Dialog>
)
+}
+
+interface ReRequestInvestigationDialogProps {
+ isOpen: boolean
+ onClose: () => void
+ onConfirm: () => Promise<void>
+ selectedCount: number
+}
+
+export function ReRequestInvestigationDialog({
+ isOpen,
+ onClose,
+ onConfirm,
+ selectedCount,
+}: ReRequestInvestigationDialogProps) {
+ const [isPending, setIsPending] = React.useState(false)
+
+ async function handleConfirm() {
+ setIsPending(true)
+ try {
+ await onConfirm()
+ } finally {
+ setIsPending(false)
+ }
+ }
+
+ return (
+ <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
+ <DialogContent>
+ <DialogHeader>
+ <DialogTitle>실사 재의뢰</DialogTitle>
+ <DialogDescription>
+ 선택한 {selectedCount}개 협력업체의 실사를 재의뢰하시겠습니까?
+ 취소 상태인 실사만 재의뢰할 수 있습니다.
+ </DialogDescription>
+ </DialogHeader>
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={onClose}
+ disabled={isPending}
+ >
+ 취소
+ </Button>
+ <Button
+ variant="default"
+ onClick={handleConfirm}
+ disabled={isPending}
+ >
+ {isPending ? "처리 중..." : "실사 재의뢰"}
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ )
}
\ No newline at end of file diff --git a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx index 8398c2e7..95cdd4d1 100644 --- a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx +++ b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx @@ -2,7 +2,7 @@ import * as React from "react"
import { type Table } from "@tanstack/react-table"
-import { Download, ClipboardCheck, X, Send } from "lucide-react"
+import { Download, ClipboardCheck, X, Send, RefreshCw } from "lucide-react"
import { toast } from "sonner"
import { exportTableToExcel } from "@/lib/export"
@@ -12,10 +12,11 @@ import { requestInvestigationAction,
cancelInvestigationAction,
sendInvestigationResultsAction,
- getFactoryLocationAnswer
+ getFactoryLocationAnswer,
+ reRequestInvestigationAction
} from "@/lib/pq/service"
import { RequestInvestigationDialog } from "./request-investigation-dialog"
-import { CancelInvestigationDialog } from "./cancel-investigation-dialog"
+import { CancelInvestigationDialog, ReRequestInvestigationDialog } from "./cancel-investigation-dialog"
import { SendResultsDialog } from "./send-results-dialog"
interface VendorsTableToolbarActionsProps {
@@ -39,6 +40,7 @@ export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActions const [isRequestDialogOpen, setIsRequestDialogOpen] = React.useState(false)
const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false)
const [isSendResultsDialogOpen, setIsSendResultsDialogOpen] = React.useState(false)
+ const [isReRequestDialogOpen, setIsReRequestDialogOpen] = React.useState(false)
// 초기 데이터 상태
const [dialogInitialData, setDialogInitialData] = React.useState<InvestigationInitialData | undefined>(undefined)
@@ -219,6 +221,41 @@ const handleOpenRequestDialog = async () => { }
}
+ // 실사 재의뢰 처리
+ const handleReRequestInvestigation = async () => {
+ setIsLoading(true)
+ try {
+ // 취소된 실사만 필터링
+ const canceledInvestigations = selectedRows.filter(row =>
+ row.original.investigation &&
+ row.original.investigation.investigationStatus === "CANCELED"
+ )
+
+ if (canceledInvestigations.length === 0) {
+ toast.error("재의뢰할 수 있는 실사가 없습니다. 취소 상태의 실사만 재의뢰할 수 있습니다.")
+ return
+ }
+
+ // 서버 액션 호출
+ const result = await reRequestInvestigationAction(
+ canceledInvestigations.map(row => row.original.investigation!.id)
+ )
+
+ if (result.success) {
+ toast.success(`${result.count}개 업체에 대한 실사가 재의뢰되었습니다.`)
+ window.location.reload()
+ } else {
+ toast.error(result.error || "실사 재의뢰 처리 중 오류가 발생했습니다.")
+ }
+ } catch (error) {
+ console.error("실사 재의뢰 중 오류 발생:", error)
+ toast.error("실사 재의뢰 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ setIsReRequestDialogOpen(false)
+ }
+ }
+
// 실사 결과 발송 처리
const handleSendInvestigationResults = async (data: { purchaseComment?: string }) => {
try {
@@ -277,6 +314,12 @@ const handleOpenRequestDialog = async () => { row.original.investigation.evaluationResult === "APPROVED"
).length
+ // 취소된 실사 수 확인
+ const canceledInvestigationsCount = selectedRows.filter(row =>
+ row.original.investigation &&
+ row.original.investigation.investigationStatus === "CANCELED"
+ ).length
+
// 미실사 PQ가 선택되었는지 확인
const hasNonInspectionPQ = selectedRows.some(row =>
row.original.type === "NON_INSPECTION"
@@ -403,6 +446,18 @@ const handleOpenRequestDialog = async () => { <span className="hidden sm:inline">실사 취소</span>
</Button>
+ {/* 실사 재의뢰 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => setIsReRequestDialogOpen(true)}
+ disabled={isLoading || selectedRows.length === 0}
+ className="gap-2"
+ >
+ <RefreshCw className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">실사 재의뢰</span>
+ </Button>
+
{/* 실사 결과 발송 버튼 */}
<Button
variant="outline"
@@ -450,6 +505,14 @@ const handleOpenRequestDialog = async () => { selectedCount={plannedInvestigationsCount}
/>
+ {/* 실사 재의뢰 Dialog */}
+ <ReRequestInvestigationDialog
+ isOpen={isReRequestDialogOpen}
+ onClose={() => setIsReRequestDialogOpen(false)}
+ onConfirm={handleReRequestInvestigation}
+ selectedCount={canceledInvestigationsCount}
+ />
+
{/* 결과 발송 Dialog */}
<SendResultsDialog
isOpen={isSendResultsDialogOpen}
diff --git a/lib/pq/service.ts b/lib/pq/service.ts index 40d81302..5870a77f 100644 --- a/lib/pq/service.ts +++ b/lib/pq/service.ts @@ -2527,6 +2527,53 @@ export async function cancelInvestigationAction(investigationIds: number[]) { }
}
+// 실사 재의뢰 서버 액션
+export async function reRequestInvestigationAction(investigationIds: number[]) {
+ try {
+ const session = await getServerSession(authOptions)
+ const userId = session?.user?.id ? Number(session.user.id) : null
+
+ if (!userId) {
+ return { success: false, error: "인증된 사용자만 실사를 재의뢰할 수 있습니다." }
+ }
+
+ const result = await db.transaction(async (tx) => {
+ // CANCELED 상태인 실사만 재의뢰 가능
+ const updatedInvestigations = await tx
+ .update(vendorInvestigations)
+ .set({
+ investigationStatus: "PLANNED",
+ updatedAt: new Date(),
+ })
+ .where(
+ and(
+ inArray(vendorInvestigations.id, investigationIds),
+ eq(vendorInvestigations.investigationStatus, "CANCELED")
+ )
+ )
+ .returning()
+
+ return updatedInvestigations
+ })
+
+ // 캐시 무효화
+ revalidateTag("vendor-investigations")
+ revalidateTag("pq-submissions")
+
+ return {
+ success: true,
+ count: result.length,
+ data: result
+ }
+ } catch (err) {
+ console.error("실사 재의뢰 중 오류 발생:", err)
+ return {
+ success: false,
+ error: err instanceof Error ? err.message : "알 수 없는 오류가 발생했습니다."
+ }
+ }
+}
+
// 실사 결과 발송 서버 액션
export async function sendInvestigationResultsAction(input: {
investigationIds: number[];
|
