diff options
Diffstat (limited to 'lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx')
| -rw-r--r-- | lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx | 663 |
1 files changed, 329 insertions, 334 deletions
diff --git a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx index f40a41f7..f879f065 100644 --- a/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx +++ b/lib/vendor-regular-registrations/table/vendor-regular-registrations-table-toolbar-actions.tsx @@ -1,334 +1,329 @@ -"use client"
-
-import { type Table } from "@tanstack/react-table"
-import { toast } from "sonner"
-
-import { Button } from "@/components/ui/button"
-import { Mail, FileWarning, Scale, FileText } from "lucide-react"
-import type { VendorRegularRegistration } from "@/config/vendorRegularRegistrationsColumnsConfig"
-import {
- sendMissingContractRequestEmails,
- sendAdditionalInfoRequestEmails,
- skipLegalReview
-} from "../service"
-import { useState } from "react"
-import { SkipReasonDialog } from "@/components/vendor-regular-registrations/skip-reason-dialog"
-import { RegistrationRequestDialog } from "@/components/vendor-regular-registrations/registration-request-dialog"
-import { ApprovalPreviewDialog } from "@/components/approval/ApprovalPreviewDialog"
-import { useRouter } from "next/navigation"
-import { useSession } from "next-auth/react"
-import { registerVendorWithApproval } from "../approval-actions"
-import { mapRegistrationToTemplateVariables } from "../handlers"
-import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog"
-
-interface VendorRegularRegistrationsTableToolbarActionsProps {
- table: Table<VendorRegularRegistration>
-}
-
-export function VendorRegularRegistrationsTableToolbarActions({
- table,
-}: VendorRegularRegistrationsTableToolbarActionsProps) {
- const router = useRouter()
- const { data: session } = useSession()
-
- const [syncLoading, setSyncLoading] = useState<{
- missingContract: boolean;
- additionalInfo: boolean;
- legalSkip: boolean;
- registrationRequest: boolean;
- }>({
- missingContract: false,
- additionalInfo: false,
- legalSkip: false,
- registrationRequest: false,
- })
-
- const [skipDialogs, setSkipDialogs] = useState<{
- legalReview: boolean;
- }>({
- legalReview: false,
- })
-
- // 2-step 결재 프로세스를 위한 상태
- const [registrationRequestDialog, setRegistrationRequestDialog] = useState<{
- open: boolean;
- registration: VendorRegularRegistration | null;
- }>({
- open: false,
- registration: null,
- })
-
- const [approvalDialog, setApprovalDialog] = useState<{
- open: boolean;
- registration: VendorRegularRegistration | null;
- }>({
- open: false,
- registration: null,
- })
-
- // 결재를 위한 중간 상태 저장
- const [registrationFormData, setRegistrationFormData] = useState<RegistrationRequestData | null>(null)
- const [approvalVariables, setApprovalVariables] = useState<Record<string, string>>({})
-
- const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original)
-
-
-
- const handleSendMissingContractRequest = async () => {
- if (selectedRows.length === 0) {
- toast.error("이메일을 발송할 업체를 선택해주세요.")
- return
- }
-
- setSyncLoading(prev => ({ ...prev, missingContract: true }))
- try {
- const vendorIds = selectedRows.map(row => row.vendorId)
- const result = await sendMissingContractRequestEmails(vendorIds)
-
- if (result.success) {
- toast.success(result.message)
- } else {
- toast.error(result.error)
- }
- } catch (error) {
- console.error("Error sending missing contract request:", error)
- toast.error("누락계약요청 이메일 발송 중 오류가 발생했습니다.")
- } finally {
- setSyncLoading(prev => ({ ...prev, missingContract: false }))
- }
- }
-
- const handleSendAdditionalInfoRequest = async () => {
- if (selectedRows.length === 0) {
- toast.error("이메일을 발송할 업체를 선택해주세요.")
- return
- }
-
- setSyncLoading(prev => ({ ...prev, additionalInfo: true }))
- try {
- const vendorIds = selectedRows.map(row => row.vendorId)
- const result = await sendAdditionalInfoRequestEmails(vendorIds)
-
- if (result.success) {
- toast.success(result.message)
- } else {
- toast.error(result.error)
- }
- } catch (error) {
- console.error("Error sending additional info request:", error)
- toast.error("추가정보요청 이메일 발송 중 오류가 발생했습니다.")
- } finally {
- setSyncLoading(prev => ({ ...prev, additionalInfo: false }))
- }
- }
-
- const handleLegalReviewSkip = async (reason: string) => {
- const cpReviewRows = selectedRows.filter(row => row.status === "cp_review");
- if (cpReviewRows.length === 0) {
- toast.error("CP검토 상태인 업체를 선택해주세요.");
- return;
- }
-
- setSyncLoading(prev => ({ ...prev, legalSkip: true }));
- try {
- const vendorIds = cpReviewRows.map(row => row.vendorId);
- const result = await skipLegalReview(vendorIds, reason);
-
- if (result.success) {
- toast.success(result.message);
- router.refresh();
- } else {
- toast.error(result.error);
- }
- } catch (error) {
- console.error("Error skipping legal review:", error);
- toast.error("법무검토 Skip 처리 중 오류가 발생했습니다.");
- } finally {
- setSyncLoading(prev => ({ ...prev, legalSkip: false }));
- }
- };
-
- // 등록요청 핸들러 - Step 1: 정보 입력
- const handleRegistrationRequest = () => {
- const approvalReadyRows = selectedRows.filter(row => row.status === "approval_ready");
-
- if (approvalReadyRows.length === 0) {
- toast.error("조건충족 상태의 벤더를 선택해주세요.");
- return;
- }
-
- if (approvalReadyRows.length > 1) {
- toast.error("정규업체 등록 요청은 한 번에 하나씩만 가능합니다.");
- return;
- }
-
- setRegistrationRequestDialog({
- open: true,
- registration: approvalReadyRows[0],
- });
- };
-
- // 등록요청 정보 입력 완료 - Step 1에서 Step 2로 전환
- const handleRegistrationRequestSubmit = async (requestData: RegistrationRequestData) => {
- if (!registrationRequestDialog.registration || !session?.user) return;
-
- try {
- // 폼 데이터 저장
- setRegistrationFormData(requestData);
-
- // 결재 템플릿 변수 생성
- const requestedAt = new Date();
- const variables = await mapRegistrationToTemplateVariables({
- requestData,
- requestedAt,
- });
-
- setApprovalVariables(variables);
-
- // RegistrationRequestDialog 닫고 ApprovalPreviewDialog 열기
- setRegistrationRequestDialog({ open: false, registration: null });
- setApprovalDialog({
- open: true,
- registration: registrationRequestDialog.registration,
- });
- } catch (error) {
- console.error("결재 준비 중 오류 발생:", error);
- toast.error("결재 준비 중 오류가 발생했습니다.");
- }
- };
-
- // 결재 상신 - Step 2: 결재선 선택 후 최종 상신
- const handleApprovalSubmit = async (approvers: any[]) => {
- if (!approvalDialog.registration || !registrationFormData || !session?.user) {
- toast.error("세션 정보가 없습니다.");
- return;
- }
-
- setSyncLoading(prev => ({ ...prev, registrationRequest: true }));
- try {
- // 결재선에서 EP ID 추출 (상신자 제외)
- const approverEpIds = approvers
- .filter((line) => line.seq !== "0" && line.epId)
- .map((line) => line.epId!);
-
- // 결재 워크플로우 시작
- const result = await registerVendorWithApproval({
- registrationId: approvalDialog.registration.id,
- requestData: registrationFormData,
- vendorId: approvalDialog.registration.vendorId, // vendors 테이블에서 정보를 가져오기 위한 vendorId
- currentUser: {
- id: Number(session.user.id),
- epId: session.user.epId || null,
- email: session.user.email || undefined,
- },
- approvers: approverEpIds,
- });
-
- if (result.status === 'pending_approval') {
- // 성공 시에만 상태 초기화 및 페이지 리로드
- setRegistrationFormData(null);
- setApprovalVariables({});
- setApprovalDialog({ open: false, registration: null });
- toast.success("정규업체 등록 결재가 상신되었습니다.");
- router.refresh();
- }
- } catch (error) {
- console.error("결재 상신 중 오류:", error);
- toast.error("결재 상신 중 오류가 발생했습니다.");
- } finally {
- setSyncLoading(prev => ({ ...prev, registrationRequest: false }));
- }
- };
-
- // CP검토 상태인 선택된 행들 개수
- const cpReviewCount = selectedRows.filter(row => row.status === "cp_review").length;
-
- // 조건충족 상태인 선택된 행들 개수
- const approvalReadyCount = selectedRows.filter(row => row.status === "approval_ready").length;
-
- return (
- <div className="flex items-center gap-2">
- <Button
- variant="outline"
- size="sm"
- onClick={handleSendMissingContractRequest}
- disabled={syncLoading.missingContract || selectedRows.length === 0}
- >
- <FileWarning className="mr-2 h-4 w-4" />
- {syncLoading.missingContract ? "발송 중..." : "누락계약요청"}
- </Button>
-
- <Button
- variant="outline"
- size="sm"
- onClick={handleSendAdditionalInfoRequest}
- disabled={syncLoading.additionalInfo || selectedRows.length === 0}
- >
- <Mail className="mr-2 h-4 w-4" />
- {syncLoading.additionalInfo ? "발송 중..." : "추가정보요청"}
- </Button>
-
- <Button
- variant="outline"
- size="sm"
- onClick={() => setSkipDialogs(prev => ({ ...prev, legalReview: true }))}
- disabled={syncLoading.legalSkip || cpReviewCount === 0}
- >
- <Scale className="mr-2 h-4 w-4" />
- {syncLoading.legalSkip ? "처리 중..." : "GTC Skip"}
- </Button>
-
- <Button
- variant="default"
- size="sm"
- onClick={handleRegistrationRequest}
- disabled={syncLoading.registrationRequest || approvalReadyCount === 0}
- >
- <FileText className="mr-2 h-4 w-4" />
- {syncLoading.registrationRequest ? "처리 중..." : "등록요청"}
- </Button>
-
- <SkipReasonDialog
- open={skipDialogs.legalReview}
- onOpenChange={(open) => setSkipDialogs(prev => ({ ...prev, legalReview: open }))}
- title="GTC Skip"
- description={`선택된 ${cpReviewCount}개 업체의 GTC를 Skip하고 CP완료 상태로 변경합니다. Skip 사유를 입력해주세요.`}
- onConfirm={handleLegalReviewSkip}
- loading={syncLoading.legalSkip}
- />
-
- <RegistrationRequestDialog
- open={registrationRequestDialog.open}
- onOpenChange={(open) => setRegistrationRequestDialog(prev => ({ ...prev, open }))}
- registration={registrationRequestDialog.registration}
- onSubmit={handleRegistrationRequestSubmit}
- />
-
- {/* 결재 미리보기 Dialog - 정규업체 등록 */}
- {session?.user && approvalDialog.registration && (
- <ApprovalPreviewDialog
- open={approvalDialog.open}
- onOpenChange={(open) => {
- setApprovalDialog(prev => ({ ...prev, open }));
- if (!open) {
- // 다이얼로그가 닫히면 폼 데이터도 초기화
- setRegistrationFormData(null);
- setApprovalVariables({});
- }
- }}
- templateName="정규업체 등록"
- variables={approvalVariables}
- title={`정규업체 등록 - ${approvalDialog.registration.companyName}`}
- description={`${approvalDialog.registration.companyName} 정규업체 등록 요청`}
- currentUser={{
- id: Number(session.user.id),
- epId: session.user.epId || null,
- name: session.user.name || null,
- email: session.user.email || '',
- }}
- onSubmit={handleApprovalSubmit}
- />
- )}
- </div>
- )
-}
+"use client" + +import { type Table } from "@tanstack/react-table" +import { toast } from "sonner" + +import { Button } from "@/components/ui/button" +import { Mail, FileWarning, Scale, FileText } from "lucide-react" +import type { VendorRegularRegistration } from "@/config/vendorRegularRegistrationsColumnsConfig" +import { + sendMissingContractRequestEmails, + sendAdditionalInfoRequestEmails, + skipLegalReview +} from "../service" +import { useState } from "react" +import { SkipReasonDialog } from "@/components/vendor-regular-registrations/skip-reason-dialog" +import { RegistrationRequestDialog } from "@/components/vendor-regular-registrations/registration-request-dialog" +import { ApprovalPreviewDialog } from "@/lib/approval/approval-preview-dialog" +import { useRouter } from "next/navigation" +import { useSession } from "next-auth/react" +import { registerVendorWithApproval } from "../approval-actions" +import { mapRegistrationToTemplateVariables } from "../handlers" +import type { RegistrationRequestData } from "@/components/vendor-regular-registrations/registration-request-dialog" + +interface VendorRegularRegistrationsTableToolbarActionsProps { + table: Table<VendorRegularRegistration> +} + +export function VendorRegularRegistrationsTableToolbarActions({ + table, +}: VendorRegularRegistrationsTableToolbarActionsProps) { + const router = useRouter() + const { data: session } = useSession() + + const [syncLoading, setSyncLoading] = useState<{ + missingContract: boolean; + additionalInfo: boolean; + legalSkip: boolean; + registrationRequest: boolean; + }>({ + missingContract: false, + additionalInfo: false, + legalSkip: false, + registrationRequest: false, + }) + + const [skipDialogs, setSkipDialogs] = useState<{ + legalReview: boolean; + }>({ + legalReview: false, + }) + + // 2-step 결재 프로세스를 위한 상태 + const [registrationRequestDialog, setRegistrationRequestDialog] = useState<{ + open: boolean; + registration: VendorRegularRegistration | null; + }>({ + open: false, + registration: null, + }) + + const [approvalDialog, setApprovalDialog] = useState<{ + open: boolean; + registration: VendorRegularRegistration | null; + }>({ + open: false, + registration: null, + }) + + // 결재를 위한 중간 상태 저장 + const [registrationFormData, setRegistrationFormData] = useState<RegistrationRequestData | null>(null) + const [approvalVariables, setApprovalVariables] = useState<Record<string, string>>({}) + + const selectedRows = table.getFilteredSelectedRowModel().rows.map(row => row.original) + + + + const handleSendMissingContractRequest = async () => { + if (selectedRows.length === 0) { + toast.error("이메일을 발송할 업체를 선택해주세요.") + return + } + + setSyncLoading(prev => ({ ...prev, missingContract: true })) + try { + const vendorIds = selectedRows.map(row => row.vendorId) + const result = await sendMissingContractRequestEmails(vendorIds) + + if (result.success) { + toast.success(result.message) + } else { + toast.error(result.error) + } + } catch (error) { + console.error("Error sending missing contract request:", error) + toast.error("누락계약요청 이메일 발송 중 오류가 발생했습니다.") + } finally { + setSyncLoading(prev => ({ ...prev, missingContract: false })) + } + } + + const handleSendAdditionalInfoRequest = async () => { + if (selectedRows.length === 0) { + toast.error("이메일을 발송할 업체를 선택해주세요.") + return + } + + setSyncLoading(prev => ({ ...prev, additionalInfo: true })) + try { + const vendorIds = selectedRows.map(row => row.vendorId) + const result = await sendAdditionalInfoRequestEmails(vendorIds) + + if (result.success) { + toast.success(result.message) + } else { + toast.error(result.error) + } + } catch (error) { + console.error("Error sending additional info request:", error) + toast.error("추가정보요청 이메일 발송 중 오류가 발생했습니다.") + } finally { + setSyncLoading(prev => ({ ...prev, additionalInfo: false })) + } + } + + const handleLegalReviewSkip = async (reason: string) => { + const cpReviewRows = selectedRows.filter(row => row.status === "cp_review"); + if (cpReviewRows.length === 0) { + toast.error("CP검토 상태인 업체를 선택해주세요."); + return; + } + + setSyncLoading(prev => ({ ...prev, legalSkip: true })); + try { + const vendorIds = cpReviewRows.map(row => row.vendorId); + const result = await skipLegalReview(vendorIds, reason); + + if (result.success) { + toast.success(result.message); + router.refresh(); + } else { + toast.error(result.error); + } + } catch (error) { + console.error("Error skipping legal review:", error); + toast.error("법무검토 Skip 처리 중 오류가 발생했습니다."); + } finally { + setSyncLoading(prev => ({ ...prev, legalSkip: false })); + } + }; + + // 등록요청 핸들러 - Step 1: 정보 입력 + const handleRegistrationRequest = () => { + const approvalReadyRows = selectedRows.filter(row => row.status === "approval_ready"); + + if (approvalReadyRows.length === 0) { + toast.error("조건충족 상태의 벤더를 선택해주세요."); + return; + } + + if (approvalReadyRows.length > 1) { + toast.error("정규업체 등록 요청은 한 번에 하나씩만 가능합니다."); + return; + } + + setRegistrationRequestDialog({ + open: true, + registration: approvalReadyRows[0], + }); + }; + + // 등록요청 정보 입력 완료 - Step 1에서 Step 2로 전환 + const handleRegistrationRequestSubmit = async (requestData: RegistrationRequestData) => { + if (!registrationRequestDialog.registration || !session?.user) return; + + try { + // 폼 데이터 저장 + setRegistrationFormData(requestData); + + // 결재 템플릿 변수 생성 (vendorId 포함) + const requestedAt = new Date(); + const variables = await mapRegistrationToTemplateVariables({ + requestData, + requestedAt, + vendorId: registrationRequestDialog.registration.vendorId, // vendors 테이블에서 추가 정보 가져오기 + }); + + setApprovalVariables(variables); + + // RegistrationRequestDialog 닫고 ApprovalPreviewDialog 열기 + setRegistrationRequestDialog({ open: false, registration: null }); + setApprovalDialog({ + open: true, + registration: registrationRequestDialog.registration, + }); + } catch (error) { + console.error("결재 준비 중 오류 발생:", error); + toast.error("결재 준비 중 오류가 발생했습니다."); + } + }; + + // 결재 상신 - Step 2: 결재선 선택 후 최종 상신 + const handleApprovalSubmit = async ({ approvers, title, attachments }: { approvers: string[], title: string, attachments?: File[] }) => { + if (!approvalDialog.registration || !registrationFormData || !session?.user) { + toast.error("세션 정보가 없습니다."); + return; + } + + setSyncLoading(prev => ({ ...prev, registrationRequest: true })); + try { + // 결재 워크플로우 시작 (approvers는 이미 EP ID 배열) + const result = await registerVendorWithApproval({ + registrationId: approvalDialog.registration.id, + requestData: registrationFormData, + vendorId: approvalDialog.registration.vendorId, // vendors 테이블에서 정보를 가져오기 위한 vendorId + currentUser: { + id: Number(session.user.id), + epId: session.user.epId || null, + email: session.user.email || undefined, + }, + approvers: approvers, + }); + + if (result.status === 'pending_approval') { + // 성공 시에만 상태 초기화 및 페이지 리로드 + setRegistrationFormData(null); + setApprovalVariables({}); + setApprovalDialog({ open: false, registration: null }); + toast.success("정규업체 등록 결재가 상신되었습니다."); + router.refresh(); + } + } catch (error) { + console.error("결재 상신 중 오류:", error); + toast.error("결재 상신 중 오류가 발생했습니다."); + } finally { + setSyncLoading(prev => ({ ...prev, registrationRequest: false })); + } + }; + + // CP검토 상태인 선택된 행들 개수 + const cpReviewCount = selectedRows.filter(row => row.status === "cp_review").length; + + // 조건충족 상태인 선택된 행들 개수 + const approvalReadyCount = selectedRows.filter(row => row.status === "approval_ready").length; + + return ( + <div className="flex items-center gap-2"> + <Button + variant="outline" + size="sm" + onClick={handleSendMissingContractRequest} + disabled={syncLoading.missingContract || selectedRows.length === 0} + > + <FileWarning className="mr-2 h-4 w-4" /> + {syncLoading.missingContract ? "발송 중..." : "누락계약요청"} + </Button> + + <Button + variant="outline" + size="sm" + onClick={handleSendAdditionalInfoRequest} + disabled={syncLoading.additionalInfo || selectedRows.length === 0} + > + <Mail className="mr-2 h-4 w-4" /> + {syncLoading.additionalInfo ? "발송 중..." : "추가정보요청"} + </Button> + + <Button + variant="outline" + size="sm" + onClick={() => setSkipDialogs(prev => ({ ...prev, legalReview: true }))} + disabled={syncLoading.legalSkip || cpReviewCount === 0} + > + <Scale className="mr-2 h-4 w-4" /> + {syncLoading.legalSkip ? "처리 중..." : "GTC Skip"} + </Button> + + <Button + variant="default" + size="sm" + onClick={handleRegistrationRequest} + disabled={syncLoading.registrationRequest || approvalReadyCount === 0} + > + <FileText className="mr-2 h-4 w-4" /> + {syncLoading.registrationRequest ? "처리 중..." : "등록요청"} + </Button> + + <SkipReasonDialog + open={skipDialogs.legalReview} + onOpenChange={(open) => setSkipDialogs(prev => ({ ...prev, legalReview: open }))} + title="GTC Skip" + description={`선택된 ${cpReviewCount}개 업체의 GTC를 Skip하고 CP완료 상태로 변경합니다. Skip 사유를 입력해주세요.`} + onConfirm={handleLegalReviewSkip} + loading={syncLoading.legalSkip} + /> + + <RegistrationRequestDialog + open={registrationRequestDialog.open} + onOpenChange={(open) => setRegistrationRequestDialog(prev => ({ ...prev, open }))} + registration={registrationRequestDialog.registration} + onSubmit={handleRegistrationRequestSubmit} + /> + + {/* 결재 미리보기 Dialog - 정규업체 등록 */} + {session?.user && session.user.epId && approvalDialog.registration && ( + <ApprovalPreviewDialog + open={approvalDialog.open} + onOpenChange={(open) => { + setApprovalDialog(prev => ({ ...prev, open })); + if (!open) { + // 다이얼로그가 닫히면 폼 데이터도 초기화 + setRegistrationFormData(null); + setApprovalVariables({}); + } + }} + templateName="정규업체 등록" + variables={approvalVariables} + title={`정규업체 등록 - ${approvalDialog.registration.companyName}`} + currentUser={{ + id: Number(session.user.id), + epId: session.user.epId, + name: session.user.name || undefined, + email: session.user.email || undefined, + }} + onConfirm={handleApprovalSubmit} + /> + )} + </div> + ) +} |
