Merge branch 'iipbs/as/dev' into feature/0626_cw_sentry

This commit is contained in:
2026-06-26 16:28:08 +09:00
10 changed files with 518 additions and 180 deletions
@@ -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;
+88 -32
View File
@@ -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 });
},
});
}
+15 -2
View File
@@ -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 { InstlSncnApi, type InstlSncnApplySaveRequestDto } from "@/entities/instl-sncn";
import { client } from "@/shared/api";
@@ -197,8 +197,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 };
@@ -5,9 +5,10 @@ import { useRouter } from "next/navigation";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
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 +30,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 +45,7 @@ interface FloatingActionButtonsProps {
checklistFormState: ChecklistFormState;
}
type ActiveDialog = "elaprTmplt" | "rjct" | "dmndSplmnt" | null;
type ActiveDialog = "elaprTmplt" | "rjct" | "dmndSplmnt" | "elaprApproval" | null;
function FloatingActionButtons({
data,
@@ -119,7 +121,13 @@ function FloatingActionButtons({
});
},
onError: (error) => {
alert(error.message || "처리 중 오류가 발생했습니다.");
let errorMessage = "";
if (error instanceof AxiosError) {
errorMessage = error.response?.data?.message ?? "";
} else {
errorMessage = error.message ?? "처리 중 오류가 발생했습니다.";
}
alert(errorMessage);
},
});
} finally {
@@ -212,8 +220,6 @@ function FloatingActionButtons({
}
async function handleRjctClick() {
if (!(await confirm("반려하시겠습니까?"))) return;
const payload: DocPrgrsUpdateRequestDto = {
docNo,
docSeComCd,
@@ -257,8 +263,6 @@ function FloatingActionButtons({
return;
}
if (!(await confirm("검토결과를 회신하시겠습니까?"))) return;
const payload = buildTechReviewReplyPayload(data, checklistFormState, "ENV", "EP", "CP");
await executeAction({
@@ -308,6 +312,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");
}
@@ -344,7 +369,7 @@ function FloatingActionButtons({
</Button>
)}
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.del) && (
<Button color="red" onClick={() => void handleDelClick()} disabled={isProcessing}>
<Button color="red" onClick={handleDelClick} disabled={isProcessing}>
삭제
</Button>
)}
@@ -399,11 +424,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,38 +434,24 @@ 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>
)}
{shouldShowButton(INSTL_SNCN_VIEW_BUTTON_ACTION.grndsChck) && (
<Button
onClick={() =>
void executeAction({ action: INSTL_SNCN_VIEW_BUTTON_ACTION.grndsChck })
}
disabled={isProcessing}
>
기술검토
</Button>
)}
</div>
</div>
<ElaprTmpltPickerDialog
open={activeDialog === "elaprTmplt"}
onClose={() => setActiveDialog(null)}
onSelect={handleElaprTmpltSelect}
/>
<TextInputDialog
open={activeDialog === "dmndSplmnt"}
onOpenChange={() => setActiveDialog(null)}
@@ -453,24 +460,27 @@ 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),
});
}}
/>
</>
);
}
export { FloatingActionButtons };
async function handleStrg(
context: ActionHandlerContext<InstlSncnApplySaveRequestDto>,
): Promise<void> {
try {
await instlSncnService.saveApply(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleAply(
async function handleSaveApply(
context: ActionHandlerContext<InstlSncnApplySaveRequestDto>,
): Promise<void> {
try {
@@ -482,8 +492,12 @@ async function handleAply(
}
async function handleDel(context: ActionHandlerContext): Promise<void> {
// TODO: 삭제 API 호출
context.onSuccess?.();
try {
await instlSncnService.remove(context.data.detDto.instlSncnDocNo);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleSplmntCmptn(context: ActionHandlerContext): Promise<void> {
@@ -491,16 +505,7 @@ async function handleSplmntCmptn(context: ActionHandlerContext): Promise<void> {
context.onSuccess?.();
}
async function handleInstlSncnAncmnt(context: ActionHandlerContext): Promise<void> {
try {
await instlSncnService.changePrgrsSttsComCd(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleEnvoSbmsn(
async function handleChangePrgrsSttsComCd(
context: ActionHandlerContext<DocPrgrsUpdateRequestDto>,
): Promise<void> {
try {
@@ -511,25 +516,7 @@ async function handleEnvoSbmsn(
}
}
async function handleRjct(context: ActionHandlerContext<DocPrgrsUpdateRequestDto>): Promise<void> {
try {
await instlSncnService.changePrgrsSttsComCd(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleSncnAprv(context: ActionHandlerContext): Promise<void> {
try {
await instlSncnService.changePrgrsSttsComCd(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleRvwRply(
async function handleTechReview(
context: ActionHandlerContext<InstlSncnApplySaveRequestDto>,
): Promise<void> {
try {
@@ -540,40 +527,17 @@ async function handleRvwRply(
}
}
async function handleRqstTechRvw(context: ActionHandlerContext): Promise<void> {
try {
await instlSncnService.changePrgrsSttsComCd(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleDsgntRvwr(context: ActionHandlerContext<ElaprDmndSaveRequest>): Promise<void> {
try {
await ElaprDmndApi.create(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleRvwStrg(
context: ActionHandlerContext<InstlSncnApplySaveRequestDto>,
async function handleDsgntRvwr(
context: ActionHandlerContext<ElaprDmndSaveRequestDto>,
): Promise<void> {
try {
await instlSncnService.techReview(context.data.detDto.instlSncnDocNo, context.request);
await elaprDmndService.save(context.request);
context.onSuccess?.();
} catch (error) {
context.onError?.(error as Error);
}
}
async function handleRvwCmptn(context: ActionHandlerContext): Promise<void> {
// TODO: 검토완료 API 호출
context.onSuccess?.();
}
async function handleDmndSplmnt(context: ActionHandlerContext): Promise<void> {
// TODO: 보완요청 API 호출
context.onSuccess?.();
@@ -595,19 +559,19 @@ async function handleGrndsChck(context: ActionHandlerContext): Promise<void> {
}
const buttonActionHandlers: Record<InstlSncnViewButtonAction, ActionHandler> = {
strg: handleStrg,
aply: handleAply,
strg: handleSaveApply,
aply: handleSaveApply,
del: handleDel,
splmntCmptn: handleSplmntCmptn,
instlSncnAncmnt: handleInstlSncnAncmnt,
envoSbmsn: handleEnvoSbmsn,
rjct: handleRjct,
sncnAprv: handleSncnAprv,
rvwRply: handleRvwRply,
rqstTechRvw: handleRqstTechRvw,
instlSncnAncmnt: handleChangePrgrsSttsComCd,
envoSbmsn: handleChangePrgrsSttsComCd,
rjct: handleChangePrgrsSttsComCd,
sncnAprv: handleChangePrgrsSttsComCd,
rvwRply: handleTechReview,
rqstTechRvw: handleChangePrgrsSttsComCd,
dsgntRvwr: handleDsgntRvwr,
rvwStrg: handleRvwStrg,
rvwCmptn: handleRvwCmptn,
rvwStrg: handleTechReview,
rvwCmptn: handleTechReview,
dmndSplmnt: handleDmndSplmnt,
atrzAprv: handleAtrzAprv,
atrzRply: handleAtrzRply,
@@ -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(() => {