[as] feat: 설치인가 버튼 로직 작업
This commit is contained in:
@@ -1,17 +1,67 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
ElaprDmndResponse,
|
||||
ElaprDmndSaveRequest,
|
||||
ElaprDmndResponseDto,
|
||||
ElaprDmndSaveRequestDto,
|
||||
ElaprDmndSearchDto,
|
||||
ElaprDmndService,
|
||||
ElaprDocPrgrsResponseDto,
|
||||
} from "../model/elapr-dmnd-interface";
|
||||
|
||||
async function create(body: ElaprDmndSaveRequest): Promise<ElaprDmndResponse> {
|
||||
const response = await client.post<ElaprDmndResponse>(ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, body);
|
||||
const BASE = ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND;
|
||||
|
||||
async function findPage(params: ElaprDmndSearchDto): Promise<PageResponse<ElaprDmndResponseDto>> {
|
||||
const response = await client.get<PageResponse<ElaprDmndResponseDto>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const ElaprDmndApi: ElaprDmndService = {
|
||||
create,
|
||||
};
|
||||
async function findById(elaprDmndId: string): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.get<ElaprDmndResponseDto>(`${BASE}/${elaprDmndId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function save(body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.post<ElaprDmndResponseDto>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(
|
||||
elaprDmndId: string,
|
||||
body: ElaprDmndSaveRequestDto,
|
||||
): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.put<ElaprDmndResponseDto>(`${BASE}/${elaprDmndId}`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function updateStatus(
|
||||
elaprDmndId: string,
|
||||
atrzSttsComCd: string,
|
||||
): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.put<ElaprDmndResponseDto>(`${BASE}/${elaprDmndId}/status`, null, {
|
||||
params: { atrzSttsComCd },
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(elaprDmndId: string): Promise<void> {
|
||||
await client.delete(`${BASE}/${elaprDmndId}`);
|
||||
}
|
||||
|
||||
async function findDocPrgrsByDocNo(elaprDocNo: string): Promise<ElaprDocPrgrsResponseDto[]> {
|
||||
const response = await client.get<ElaprDocPrgrsResponseDto[]>(
|
||||
`${BASE}/sanc/docprgrs/${elaprDocNo}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const elaprDmndService = {
|
||||
findPage,
|
||||
findById,
|
||||
save,
|
||||
update,
|
||||
updateStatus,
|
||||
remove,
|
||||
findDocPrgrsByDocNo,
|
||||
} satisfies ElaprDmndService;
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
ElaprAplnSaveRequest,
|
||||
ElaprAplnUpdateRequest,
|
||||
} from "../model/elapr-apln-interface";
|
||||
import type { ElaprDmndSaveRequestDto, ElaprDmndSearchDto } from "../model/elapr-dmnd-interface";
|
||||
import type {
|
||||
ElaprTmpltSaveRequest,
|
||||
ElaprTmpltSearchParams,
|
||||
@@ -15,10 +16,12 @@ import type {
|
||||
} from "../model/elapr-tmplt-interface";
|
||||
|
||||
import { elaprAplnService } from "./elapr-apln-service";
|
||||
import { elaprDmndService } from "./elapr-dmnd-service";
|
||||
import { elaprTmpltService } from "./elapr-tmplt-service";
|
||||
|
||||
const elaprTmpltBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_TMPLT] as const;
|
||||
const elaprAplnBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_APLN] as const;
|
||||
const elaprDmndBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND] as const;
|
||||
|
||||
export function useElaprTmpltPage(params: ElaprTmpltSearchParams) {
|
||||
return useQuery({
|
||||
@@ -50,13 +53,8 @@ export function useUpdateElaprTmplt() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprTmpltId,
|
||||
body,
|
||||
}: {
|
||||
elaprTmpltId: string;
|
||||
body: ElaprTmpltUpdateRequest;
|
||||
}) => elaprTmpltService.update(elaprTmpltId, body),
|
||||
mutationFn: ({ elaprTmpltId, body }: { elaprTmpltId: string; body: ElaprTmpltUpdateRequest }) =>
|
||||
elaprTmpltService.update(elaprTmpltId, body),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprTmpltBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -99,12 +97,8 @@ export function useSaveMultiElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
body,
|
||||
}: {
|
||||
body: ElaprAplnSaveRequest[];
|
||||
elaprTmpltId: string;
|
||||
}) => elaprAplnService.saveMulti(body),
|
||||
mutationFn: ({ body }: { body: ElaprAplnSaveRequest[]; elaprTmpltId: string }) =>
|
||||
elaprAplnService.saveMulti(body),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -118,13 +112,8 @@ export function useUpdateElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
aplnId,
|
||||
body,
|
||||
}: {
|
||||
aplnId: string;
|
||||
body: ElaprAplnUpdateRequest;
|
||||
}) => elaprAplnService.update(aplnId, body),
|
||||
mutationFn: ({ aplnId, body }: { aplnId: string; body: ElaprAplnUpdateRequest }) =>
|
||||
elaprAplnService.update(aplnId, body),
|
||||
onSuccess: (_data, { body, aplnId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -141,12 +130,8 @@ export function useDeleteElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
aplnId,
|
||||
}: {
|
||||
aplnId: string;
|
||||
elaprTmpltId: string;
|
||||
}) => elaprAplnService.remove(aplnId),
|
||||
mutationFn: ({ aplnId }: { aplnId: string; elaprTmpltId: string }) =>
|
||||
elaprAplnService.remove(aplnId),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -160,12 +145,8 @@ export function useReorderElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
body,
|
||||
}: {
|
||||
body: ElaprAplnReorderRequest;
|
||||
elaprTmpltId: string;
|
||||
}) => elaprAplnService.reorder(body),
|
||||
mutationFn: ({ body }: { body: ElaprAplnReorderRequest; elaprTmpltId: string }) =>
|
||||
elaprAplnService.reorder(body),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -174,3 +155,78 @@ export function useReorderElaprApln() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDmndPage(params: ElaprDmndSearchDto) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findPage", params],
|
||||
queryFn: () => elaprDmndService.findPage(params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDmnd(elaprDmndId: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findById", elaprDmndId],
|
||||
queryFn: () => elaprDmndService.findById(elaprDmndId!),
|
||||
enabled: Boolean(elaprDmndId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDocPrgrsByDocNo(elaprDocNo: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
queryFn: () => elaprDmndService.findDocPrgrsByDocNo(elaprDocNo!),
|
||||
enabled: Boolean(elaprDocNo),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveElaprDmnd() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (body: ElaprDmndSaveRequestDto) => elaprDmndService.save(body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateElaprDmnd() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ elaprDmndId, body }: { elaprDmndId: string; body: ElaprDmndSaveRequestDto }) =>
|
||||
elaprDmndService.update(elaprDmndId, body),
|
||||
onSuccess: (_data, { elaprDmndId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...elaprDmndBaseKey, "findById", elaprDmndId],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateElaprDmndStatus() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ elaprDmndId, atrzSttsComCd }: { elaprDmndId: string; atrzSttsComCd: string }) =>
|
||||
elaprDmndService.updateStatus(elaprDmndId, atrzSttsComCd),
|
||||
onSuccess: (_data, { elaprDmndId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...elaprDmndBaseKey, "findById", elaprDmndId],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveElaprDmnd() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (elaprDmndId: string) => elaprDmndService.remove(elaprDmndId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { elaprAplnService } from "./api/elapr-apln-service";
|
||||
export { ElaprDmndApi } from "./api/elapr-dmnd-service";
|
||||
export { elaprDmndService } from "./api/elapr-dmnd-service";
|
||||
export { elaprTmpltService } from "./api/elapr-tmplt-service";
|
||||
export {
|
||||
useCreateElaprTmplt,
|
||||
@@ -7,11 +7,18 @@ export {
|
||||
useDeleteElaprTmplt,
|
||||
useElaprApln,
|
||||
useElaprAplnPage,
|
||||
useElaprDmnd,
|
||||
useElaprDmndPage,
|
||||
useElaprDocPrgrsByDocNo,
|
||||
useElaprTmplt,
|
||||
useElaprTmpltPage,
|
||||
useRemoveElaprDmnd,
|
||||
useReorderElaprApln,
|
||||
useSaveElaprDmnd,
|
||||
useSaveMultiElaprApln,
|
||||
useUpdateElaprApln,
|
||||
useUpdateElaprDmnd,
|
||||
useUpdateElaprDmndStatus,
|
||||
useUpdateElaprTmplt,
|
||||
} from "./api/queries";
|
||||
export {
|
||||
@@ -25,7 +32,13 @@ export {
|
||||
type ElaprAplnService,
|
||||
type ElaprAplnUpdateRequest,
|
||||
} from "./model/elapr-apln-interface";
|
||||
export type { ElaprDmndSaveRequest } from "./model/elapr-dmnd-interface";
|
||||
export type {
|
||||
ElaprDmndResponseDto,
|
||||
ElaprDmndSaveRequestDto,
|
||||
ElaprDmndSearchDto,
|
||||
ElaprDmndService,
|
||||
ElaprDocPrgrsResponseDto,
|
||||
} from "./model/elapr-dmnd-interface";
|
||||
export type {
|
||||
ElaprTmplt,
|
||||
ElaprTmpltSaveRequest,
|
||||
|
||||
@@ -1,22 +1,62 @@
|
||||
export interface ElaprDmndSaveRequest {
|
||||
import type { PageableParams, PageResponse } from "@/shared/model";
|
||||
|
||||
/** 백엔드 ElaprDmndSaveRequestDto */
|
||||
export interface ElaprDmndSaveRequestDto {
|
||||
elaprDocNo: string;
|
||||
elaprTmpltId: string;
|
||||
dmndTtl: string;
|
||||
dmndCn: string;
|
||||
}
|
||||
|
||||
export interface ElaprDmndResponse {
|
||||
/** 백엔드 ElaprDmndResponseDto */
|
||||
export interface ElaprDmndResponseDto {
|
||||
elaprDmndId: string;
|
||||
rqstrUuid?: string;
|
||||
elaprDocNo: string;
|
||||
elaprTmpltId: string;
|
||||
dmndTtl: string;
|
||||
dmndCn: string;
|
||||
atrzSttsComCd: string;
|
||||
rqstrUuid: string;
|
||||
regDt: string;
|
||||
updtDt: string;
|
||||
atrzSttsComNm?: string;
|
||||
dmndDt?: string;
|
||||
updtDt?: string;
|
||||
}
|
||||
|
||||
export interface ElaprDmndService {
|
||||
create(body: ElaprDmndSaveRequest): Promise<ElaprDmndResponse>;
|
||||
/** 백엔드 ElaprDmndSearchDto + Pageable */
|
||||
export interface ElaprDmndSearchDto extends PageableParams {
|
||||
rqstrUuid?: string;
|
||||
atrzSttsComCd?: string;
|
||||
dmndTtl?: string;
|
||||
dmndDtStart?: string;
|
||||
dmndDtEnd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprDocPrgrsResponseDto */
|
||||
export interface ElaprDocPrgrsResponseDto {
|
||||
elaprDocNo: string;
|
||||
elaprDmndId: string;
|
||||
elaprPrgrsId: string;
|
||||
atrzSeqNo?: number;
|
||||
atrzSeComCd: string;
|
||||
atrzSeComNm?: string;
|
||||
aprvrUuid?: string;
|
||||
aprvrUuidNm?: string;
|
||||
atrzPrgrsSttsComCd: string;
|
||||
atrzPrgrsSttsComNm?: string;
|
||||
rjctRsn?: string;
|
||||
atrzDt?: string;
|
||||
jbpsSeNm?: string;
|
||||
jbgdSeNm?: string;
|
||||
ognzNm?: string;
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface ElaprDmndService {
|
||||
findPage(params: ElaprDmndSearchDto): Promise<PageResponse<ElaprDmndResponseDto>>;
|
||||
findById(elaprDmndId: string): Promise<ElaprDmndResponseDto>;
|
||||
save(body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto>;
|
||||
update(elaprDmndId: string, body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto>;
|
||||
updateStatus(elaprDmndId: string, atrzSttsComCd: string): Promise<ElaprDmndResponseDto>;
|
||||
remove(elaprDmndId: string): Promise<void>;
|
||||
findDocPrgrsByDocNo(elaprDocNo: string): Promise<ElaprDocPrgrsResponseDto[]>;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { PROXY_HOST } from "@/shared/config";
|
||||
import { formatFileSize } from "@/shared/lib/file-utils";
|
||||
import axios from "axios";
|
||||
import { processElaprPrgrs } from "@/entities/elapr/api/elapr-prgrs-service";
|
||||
import { ElaprDmndApi } from "@/entities/elapr/api/elapr-dmnd-service";
|
||||
import { elaprDmndService } from "@/entities/elapr";
|
||||
import { dayjs, useUserStore, useAlertStore } from "@/shared/lib";
|
||||
import { instlSncnService, type InstlSncnApplySaveRequestDto } from "@/entities/instl-sncn";
|
||||
import { client } from "@/shared/api";
|
||||
@@ -206,8 +206,7 @@ function CnfmViewPage({ id }: { id: string }) {
|
||||
dmndTtl: det.bizNm /* "구례 하수도 처리시설 2026", */,
|
||||
dmndCn: det.bizCn /*"구례 발주 하수도 처리시설 사업 2026년도 진행분 결제 요청 입니다."*/,
|
||||
};
|
||||
// ElaprDmndApi 서비스를 통해 백엔드 연동
|
||||
await ElaprDmndApi.create(payload);
|
||||
await elaprDmndService.save(payload);
|
||||
alert("결재자 지정(결재요청 등록)이 완료되었습니다.");
|
||||
try {
|
||||
await query.refetch();
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useElaprDocPrgrsByDocNo } from "@/entities/elapr";
|
||||
import { processElaprPrgrs } from "@/entities/elapr/api/elapr-prgrs-service";
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import { getApproverStatus, sortByAtrzSeqNo } from "../../lib/approval-utils";
|
||||
|
||||
interface ElaprApprovalDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
elaprDocNo: string;
|
||||
currentUserUuid: string;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
function ElaprApprovalDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
elaprDocNo,
|
||||
currentUserUuid,
|
||||
onSuccess,
|
||||
}: ElaprApprovalDialogProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [rjctRsn, setRjctRsn] = useState("");
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
const { data: elaprDocPrgrsList = [], isLoading } = useElaprDocPrgrsByDocNo(
|
||||
open ? elaprDocNo : null,
|
||||
);
|
||||
|
||||
const orderedApprovers = sortByAtrzSeqNo(elaprDocPrgrsList);
|
||||
|
||||
const currentUserPrgrs = elaprDocPrgrsList.find(
|
||||
(row) => row.aprvrUuid === currentUserUuid && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002",
|
||||
);
|
||||
const canApprove = Boolean(currentUserPrgrs?.elaprPrgrsId);
|
||||
|
||||
async function handleApprove() {
|
||||
if (!currentUserPrgrs?.elaprPrgrsId) return;
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
await processElaprPrgrs(currentUserPrgrs.elaprPrgrsId, "ATRZ_PRGRS_003");
|
||||
alert("결재가 완료되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
});
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
let errorMessage = "";
|
||||
if (error instanceof AxiosError) {
|
||||
errorMessage = error.response?.data?.message ?? "";
|
||||
} else {
|
||||
errorMessage = "결재 처리 중 오류가 발생했습니다.";
|
||||
}
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReject() {
|
||||
if (!currentUserPrgrs?.elaprPrgrsId) return;
|
||||
if (!rjctRsn.trim()) {
|
||||
alert("반려 사유를 입력해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
await processElaprPrgrs(currentUserPrgrs.elaprPrgrsId, "ATRZ_PRGRS_004");
|
||||
alert("반려 처리가 완료되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
});
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
let errorMessage = "";
|
||||
if (error instanceof AxiosError) {
|
||||
errorMessage = error.response?.data?.message ?? "";
|
||||
} else {
|
||||
errorMessage = "반려 처리 중 오류가 발생했습니다.";
|
||||
}
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setRjctRsn("");
|
||||
onOpenChange(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-[72rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>전자결재</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h4 className="mb-3 text-xl font-semibold">결재 진행 단계</h4>
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-12 w-full" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
</div>
|
||||
) : orderedApprovers.length === 0 ? (
|
||||
<div className="text-center text-gray-500">결재 정보가 없습니다.</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="line">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{orderedApprovers.map((row) => (
|
||||
<TableHead key={String(row.elaprPrgrsId ?? row.atrzSeComCd)}>
|
||||
{row.atrzSeComNm ?? row.atrzSeComCd}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
{orderedApprovers.map((row) => {
|
||||
const status = row.atrzPrgrsSttsComCd ?? "";
|
||||
const st = getApproverStatus(status);
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={String(row.elaprPrgrsId ?? row.atrzSeComCd)}
|
||||
align="center"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-md px-2 py-1 text-base font-semibold",
|
||||
st.badgeClass,
|
||||
)}
|
||||
>
|
||||
{st.label}
|
||||
</span>
|
||||
<span className="font-semibold">{row.aprvrUuidNm ?? "-"}</span>
|
||||
</div>
|
||||
<div className="mt-2 text-base text-gray-500">
|
||||
{row.ognzNm ?? ""}
|
||||
{row.jbpsSeNm || row.jbgdSeNm
|
||||
? ` · ${row.jbpsSeNm ?? ""}${row.jbgdSeNm ? ` ${row.jbgdSeNm}` : ""}`
|
||||
: ""}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{canApprove && (
|
||||
<div>
|
||||
<h4 className="mb-3 text-xl font-semibold">반려 의견</h4>
|
||||
<Textarea
|
||||
value={rjctRsn}
|
||||
onChange={(event) => setRjctRsn(event.target.value)}
|
||||
placeholder="반려 시 사유를 입력하세요"
|
||||
rows={4}
|
||||
className="!text-xl"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" color="white" onClick={handleClose} disabled={isProcessing}>
|
||||
취소
|
||||
</Button>
|
||||
{canApprove && (
|
||||
<>
|
||||
<Button color="red" onClick={handleReject} disabled={isProcessing}>
|
||||
반려
|
||||
</Button>
|
||||
<Button onClick={handleApprove} disabled={isProcessing}>
|
||||
결재
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprApprovalDialog };
|
||||
@@ -7,7 +7,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { ElaprDmndApi, ElaprDmndSaveRequest, type ElaprTmplt } from "@/entities/elapr";
|
||||
import { type ElaprDmndSaveRequestDto, elaprDmndService, type ElaprTmplt } from "@/entities/elapr";
|
||||
import {
|
||||
type DocPrgrsUpdateRequestDto,
|
||||
type InstlSncnApplySaveRequestDto,
|
||||
@@ -29,8 +29,9 @@ import {
|
||||
buildTechReviewReplyPayload,
|
||||
computeReviewStepChecklistProgress,
|
||||
} from "../../lib/checklist-utils";
|
||||
import { type ChecklistFormState, ROLE_TO_REVIEW_STEP } from "../../model/view-types";
|
||||
import { type ChecklistFormState } from "../../model/view-types";
|
||||
|
||||
import { ElaprApprovalDialog } from "./elapr-approval-dialog";
|
||||
import { ElaprTmpltPickerDialog } from "./elapr-tmplt-picker-dialog";
|
||||
import { TextInputDialog } from "./text-input-dialog";
|
||||
|
||||
@@ -43,7 +44,7 @@ interface FloatingActionButtonsProps {
|
||||
checklistFormState: ChecklistFormState;
|
||||
}
|
||||
|
||||
type ActiveDialog = "elaprTmplt" | "rjct" | "dmndSplmnt" | null;
|
||||
type ActiveDialog = "elaprTmplt" | "rjct" | "dmndSplmnt" | "elaprApproval" | null;
|
||||
|
||||
function FloatingActionButtons({
|
||||
data,
|
||||
@@ -212,8 +213,6 @@ function FloatingActionButtons({
|
||||
}
|
||||
|
||||
async function handleRjctClick() {
|
||||
if (!(await confirm("반려하시겠습니까?"))) return;
|
||||
|
||||
const payload: DocPrgrsUpdateRequestDto = {
|
||||
docNo,
|
||||
docSeComCd,
|
||||
@@ -257,8 +256,6 @@ function FloatingActionButtons({
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await confirm("검토결과를 회신하시겠습니까?"))) return;
|
||||
|
||||
const payload = buildTechReviewReplyPayload(data, checklistFormState, "ENV", "EP", "CP");
|
||||
|
||||
await executeAction({
|
||||
@@ -308,6 +305,27 @@ function FloatingActionButtons({
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRvwCmptnClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"KECO",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토완료가 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildTechReviewReplyPayload(data, checklistFormState, "KECO", "TE", "CP");
|
||||
|
||||
await executeAction({
|
||||
action: INSTL_SNCN_VIEW_BUTTON_ACTION.rvwCmptn,
|
||||
request: payload,
|
||||
successMessage: "검토완료 처리되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
function handleDmndSplmntClick() {
|
||||
setActiveDialog("dmndSplmnt");
|
||||
}
|
||||
@@ -399,11 +417,7 @@ function FloatingActionButtons({
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.rvwCmptn) && (
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() => void executeAction({ action: INSTL_SNCN_VIEW_BUTTON_ACTION.rvwCmptn })}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Button color="green" onClick={handleRvwCmptnClick} disabled={isProcessing}>
|
||||
검토완료
|
||||
</Button>
|
||||
)}
|
||||
@@ -413,18 +427,12 @@ function FloatingActionButtons({
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.atrzAprv) && (
|
||||
<Button
|
||||
onClick={() => void executeAction({ action: INSTL_SNCN_VIEW_BUTTON_ACTION.atrzAprv })}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
|
||||
결재(승인)
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.atrzRply) && (
|
||||
<Button
|
||||
onClick={() => void executeAction({ action: INSTL_SNCN_VIEW_BUTTON_ACTION.atrzRply })}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Button onClick={() => setActiveDialog("elaprApproval")} disabled={isProcessing}>
|
||||
결재(회신)
|
||||
</Button>
|
||||
)}
|
||||
@@ -453,6 +461,19 @@ function FloatingActionButtons({
|
||||
isSubmitting={isProcessing}
|
||||
onConfirm={(value) => void handleDmndSplmntConfirm(value)}
|
||||
/>
|
||||
<ElaprApprovalDialog
|
||||
open={activeDialog === "elaprApproval"}
|
||||
onOpenChange={(open) => setActiveDialog(open ? "elaprApproval" : null)}
|
||||
elaprDocNo={data.detDto.instlSncnDocNo}
|
||||
currentUserUuid={userInfo?.userUnqId ?? ""}
|
||||
onSuccess={() => {
|
||||
setActiveDialog(null);
|
||||
alert("결재가 완료되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: instlSncnKeys.detail(instlSncnDocNo),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -549,9 +570,11 @@ async function handleRqstTechRvw(context: ActionHandlerContext): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDsgntRvwr(context: ActionHandlerContext<ElaprDmndSaveRequest>): Promise<void> {
|
||||
async function handleDsgntRvwr(
|
||||
context: ActionHandlerContext<ElaprDmndSaveRequestDto>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await ElaprDmndApi.create(context.request);
|
||||
await elaprDmndService.save(context.request);
|
||||
context.onSuccess?.();
|
||||
} catch (error) {
|
||||
context.onError?.(error as Error);
|
||||
@@ -569,9 +592,15 @@ async function handleRvwStrg(
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRvwCmptn(context: ActionHandlerContext): Promise<void> {
|
||||
// TODO: 검토완료 API 호출
|
||||
context.onSuccess?.();
|
||||
async function handleRvwCmptn(
|
||||
context: ActionHandlerContext<InstlSncnApplySaveRequestDto>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await instlSncnService.techReview(context.data.detDto.instlSncnDocNo, context.request);
|
||||
context.onSuccess?.();
|
||||
} catch (error) {
|
||||
context.onError?.(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDmndSplmnt(context: ActionHandlerContext): Promise<void> {
|
||||
|
||||
@@ -4,10 +4,7 @@ import { useUserStore } from "@/shared/lib";
|
||||
import { useInstlSncnDetail } from "@/entities/instl-sncn";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import {
|
||||
EMPTY_CKLST_RESULT_LIST,
|
||||
useChecklistFormState,
|
||||
} from "../lib/use-checklist-form-state";
|
||||
import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state";
|
||||
|
||||
import { FloatingActionButtons } from "./components/floating-action-buttons";
|
||||
import { AttachmentsSection } from "./sections/attachments-section";
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { processElaprPrgrs } from "@/entities/elapr/api/elapr-prgrs-service";
|
||||
import { ElaprDmndApi } from "@/entities/elapr/api/elapr-dmnd-service";
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/entities/instl-sncn";
|
||||
import { dayjs, useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { elaprDmndService } from "@/entities/elapr";
|
||||
import { processElaprPrgrs } from "@/entities/elapr/api/elapr-prgrs-service";
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/entities/instl-sncn";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getApproverStatus, sortByAtrzSeqNo } from "../../lib/approval-utils";
|
||||
|
||||
@@ -42,9 +41,7 @@ function ApprovalInfoSection({
|
||||
const status = row.atrzPrgrsSttsComCd ?? "";
|
||||
const st = getApproverStatus(status);
|
||||
const canApprove =
|
||||
status === "ATRZ_PRGRS_002" &&
|
||||
row.elaprPrgrsId &&
|
||||
userInfo?.userUnqId === row.aprvrUuid;
|
||||
status === "ATRZ_PRGRS_002" && row.elaprPrgrsId && userInfo?.userUnqId === row.aprvrUuid;
|
||||
|
||||
return (
|
||||
<div className="text-center">
|
||||
@@ -90,7 +87,7 @@ function ApprovalInfoSection({
|
||||
|
||||
const handleAssignApprover = async () => {
|
||||
try {
|
||||
await ElaprDmndApi.create({
|
||||
await elaprDmndService.save({
|
||||
elaprDocNo: docNo,
|
||||
elaprTmpltId: "TMPLT20260611000001",
|
||||
dmndTtl: docTitle,
|
||||
@@ -117,10 +114,7 @@ function ApprovalInfoSection({
|
||||
</th>
|
||||
))
|
||||
: orderedApprovers.map((row) => (
|
||||
<th
|
||||
key={String(row.elaprPrgrsId ?? row.atrzSeComCd)}
|
||||
className="text-center"
|
||||
>
|
||||
<th key={String(row.elaprPrgrsId ?? row.atrzSeComCd)} className="text-center">
|
||||
{row.atrzSeComNm ?? row.atrzSeComCd}
|
||||
</th>
|
||||
))}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import {
|
||||
type ChecklistFormState,
|
||||
REVIEW_STEP_LABELS,
|
||||
ROLE_TO_REVIEW_STEP,
|
||||
TASK_CODE_LABELS,
|
||||
} from "../../model/view-types";
|
||||
import { ChecklistTaskPanel } from "../components/checklist-task-panel";
|
||||
@@ -40,7 +41,8 @@ function ChecklistSection({
|
||||
);
|
||||
const taskCodes = useMemo(() => extractTaskCodes(cklstResultList), [cklstResultList]);
|
||||
|
||||
const [selectedReviewStep, setSelectedReviewStep] = useState<ReviewStepCode>("LOCAL");
|
||||
const defaultReviewStep = ROLE_TO_REVIEW_STEP[userRoleCode ?? ""] ?? "LOCAL";
|
||||
const [selectedReviewStep, setSelectedReviewStep] = useState<ReviewStepCode>(defaultReviewStep);
|
||||
const [selectedTaskCode, setSelectedTaskCode] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user