[as] feat: fnrs-cnsltn-mng 서비스 구현

This commit is contained in:
2026-06-30 13:51:22 +09:00
parent 3b4d9df0e8
commit 6fb610d9aa
6 changed files with 532 additions and 111 deletions
@@ -0,0 +1,86 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { PageResponse } from "@/shared/model";
import type {
DocPrgrsUpdateRequestDto,
FnrsCnsltnApplySaveRequestDto,
FnrsCnsltnMngDetResponseDto,
FnrsCnsltnMngResponseDto,
FnrsCnsltnMngSaveRequestDto,
FnrsCnsltnMngSearchDto,
FnrsCnsltnMngService,
FnrsCnsltnMngUpdateRequestDto,
} from "../model/fnrs-cnsltn-interface";
const BASE = ENDPOINT.AS_SERVICE.FNRS_CNSLTN_MNG;
async function findPage(
searchDto: FnrsCnsltnMngSearchDto,
): Promise<PageResponse<FnrsCnsltnMngResponseDto>> {
const response = await client.get<PageResponse<FnrsCnsltnMngResponseDto>>(BASE, {
params: searchDto,
});
return response.data;
}
async function findById(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnMngDetResponseDto> {
const response = await client.get<FnrsCnsltnMngDetResponseDto>(`${BASE}/${fnrsCnsltnDocNo}`);
return response.data;
}
async function save(body: FnrsCnsltnMngSaveRequestDto): Promise<string> {
const response = await client.post<string>(BASE, body);
return response.data;
}
async function saveApply(body: FnrsCnsltnApplySaveRequestDto): Promise<string> {
const response = await client.post<string>(`${BASE}/save-apply`, body);
return response.data;
}
async function update(
fnrsCnsltnDocNo: string,
body: FnrsCnsltnMngUpdateRequestDto,
): Promise<string> {
const response = await client.put<string>(`${BASE}/${fnrsCnsltnDocNo}`, body);
return response.data;
}
async function techReview(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<void> {
await client.post<void>(`${BASE}/${id}/tech-review`, body);
}
async function submitSupplement(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<string> {
const response = await client.post<string>(`${BASE}/${id}/submitsupplement`, body);
return response.data;
}
async function submitReqSupplement(
id: string,
body: FnrsCnsltnApplySaveRequestDto,
): Promise<string> {
const response = await client.post<string>(`${BASE}/${id}/reqsupplement`, body);
return response.data;
}
async function remove(fnrsCnsltnDocNo: string): Promise<void> {
await client.delete<void>(`${BASE}/${fnrsCnsltnDocNo}`);
}
async function changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void> {
await client.post<void>(`${BASE}/doc/updatePrgrsStts`, body);
}
export const fnrsCnsltnMngService = {
findPage,
findById,
save,
saveApply,
update,
techReview,
submitSupplement,
submitReqSupplement,
remove,
changePrgrsSttsComCd,
} satisfies FnrsCnsltnMngService;
@@ -0,0 +1,131 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type {
DocPrgrsUpdateRequestDto,
FnrsCnsltnApplySaveRequestDto,
FnrsCnsltnMngSaveRequestDto,
FnrsCnsltnMngSearchDto,
FnrsCnsltnMngUpdateRequestDto,
} from "../model/fnrs-cnsltn-interface";
import { fnrsCnsltnMngService } from "./fnrs-cnsltn-service";
const fnrsCnsltnMngBaseKey = [ENDPOINT.AS_SERVICE.FNRS_CNSLTN_MNG] as const;
export function useFnrsCnsltnMngPage(params: FnrsCnsltnMngSearchDto) {
return useQuery({
queryKey: [...fnrsCnsltnMngBaseKey, "findPage", params],
queryFn: () => fnrsCnsltnMngService.findPage(params),
});
}
export function useFnrsCnsltnMng(fnrsCnsltnDocNo: string | null | undefined) {
return useQuery({
queryKey: [...fnrsCnsltnMngBaseKey, "findById", fnrsCnsltnDocNo],
queryFn: () => fnrsCnsltnMngService.findById(fnrsCnsltnDocNo!),
enabled: Boolean(fnrsCnsltnDocNo),
});
}
export function useSaveFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: FnrsCnsltnMngSaveRequestDto) => fnrsCnsltnMngService.save(body),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
},
});
}
export function useSaveApplyFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: FnrsCnsltnApplySaveRequestDto) => fnrsCnsltnMngService.saveApply(body),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
},
});
}
export function useUpdateFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
fnrsCnsltnDocNo,
body,
}: {
fnrsCnsltnDocNo: string;
body: FnrsCnsltnMngUpdateRequestDto;
}) => fnrsCnsltnMngService.update(fnrsCnsltnDocNo, body),
onSuccess: (_data, { fnrsCnsltnDocNo }) => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
queryClient.invalidateQueries({
queryKey: [...fnrsCnsltnMngBaseKey, "findById", fnrsCnsltnDocNo],
});
},
});
}
export function useTechReviewFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, body }: { id: string; body: FnrsCnsltnApplySaveRequestDto }) =>
fnrsCnsltnMngService.techReview(id, body),
onSuccess: (_data, { id }) => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
queryClient.invalidateQueries({
queryKey: [...fnrsCnsltnMngBaseKey, "findById", id],
});
},
});
}
export function useSubmitSupplementFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, body }: { id: string; body: FnrsCnsltnApplySaveRequestDto }) =>
fnrsCnsltnMngService.submitSupplement(id, body),
onSuccess: (_data, { id }) => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
queryClient.invalidateQueries({
queryKey: [...fnrsCnsltnMngBaseKey, "findById", id],
});
},
});
}
export function useSubmitReqSupplementFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, body }: { id: string; body: FnrsCnsltnApplySaveRequestDto }) =>
fnrsCnsltnMngService.submitReqSupplement(id, body),
onSuccess: (_data, { id }) => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
queryClient.invalidateQueries({
queryKey: [...fnrsCnsltnMngBaseKey, "findById", id],
});
},
});
}
export function useDeleteFnrsCnsltnMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (fnrsCnsltnDocNo: string) => fnrsCnsltnMngService.remove(fnrsCnsltnDocNo),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
},
});
}
export function useChangePrgrsSttsComCd() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: DocPrgrsUpdateRequestDto) => fnrsCnsltnMngService.changePrgrsSttsComCd(body),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
},
});
}
+37
View File
@@ -0,0 +1,37 @@
export { fnrsCnsltnMngService } from "./api/fnrs-cnsltn-service";
export {
useChangePrgrsSttsComCd,
useDeleteFnrsCnsltnMng,
useFnrsCnsltnMng,
useFnrsCnsltnMngPage,
useSaveApplyFnrsCnsltnMng,
useSaveFnrsCnsltnMng,
useSubmitReqSupplementFnrsCnsltnMng,
useSubmitSupplementFnrsCnsltnMng,
useTechReviewFnrsCnsltnMng,
useUpdateFnrsCnsltnMng,
} from "./api/queries";
export type {
AtflMngClientResponseDto,
BizCstItem,
BizCstTreeDto,
BizSclTreeDto,
BzScaleItem,
CklstResultItem,
CklstRvwRsltTreeDto,
DocPrgrsUpdateRequestDto,
ElaprDocPrgrsClientResponseDto,
EsntlAtchFileItem,
EsntlAtchFileMngTreeResponseDto,
FnrsCnsltnApplySaveRequestDto,
FnrsCnsltnMngDetResponseDto,
FnrsCnsltnMngResponseDto,
FnrsCnsltnMngSaveRequestDto,
FnrsCnsltnMngSearchDto,
FnrsCnsltnMngService,
FnrsCnsltnMngUpdateRequestDto,
FnrsCnsltnRvwOpnnResponseDto,
FnrsCnsltnRvwOpnnSaveRequestDto,
FnrsCnsltnSplmntDmndItem,
FnrsCnsltnSplmntDmndResponseDto,
} from "./model/fnrs-cnsltn-interface";
@@ -0,0 +1,277 @@
import type { PageableParams, PageResponse } from "@/shared/model";
/** 백엔드 FnrsCnsltnMngResponseDto */
export interface FnrsCnsltnMngResponseDto {
fnrsCnsltnDocNo: string;
admdstCd?: string;
envoSeComCd?: string;
envoSeCdNm?: string;
taskSeComCd?: string;
taskSeCdNm?: string;
cfmtnBgtBizNo?: string;
bgtAplyYr?: string;
bizNm?: string;
bizBgngYr?: string;
bizEndYr?: string;
bizCn?: string;
aplcntNm?: string;
aplcntMblTelno?: string;
aplcntOgdpNm?: string;
aplcntEmlAddr?: string;
dsgnrNm?: string;
dsgnrTelno?: string;
dsgnrOgdpNm?: string;
dsgnrEmlAddr?: string;
atchFileGroupId?: string;
prgrsSttsComCd?: string;
prgrsSttsCdNm?: string;
regDt?: string;
rgtr?: string;
rgtrNm?: string;
mdfcnDt?: string;
sido?: string;
gugun?: string;
}
/** 백엔드 FnrsCnsltnMngSearchDto + Pageable */
export interface FnrsCnsltnMngSearchDto extends PageableParams {
fnrsCnsltnDocNo?: string;
admdstCd?: string;
envoSeComCd?: string;
taskSeComCd?: string;
cfmtnBgtBizNo?: string;
bgtAplyYr?: string;
bizNm?: string;
bizBgngYr?: string;
bizEndYr?: string;
bizCn?: string;
aplcntNm?: string;
aplcntMblTelno?: string;
aplcntOgdpNm?: string;
aplcntEmlAddr?: string;
dsgnrNm?: string;
dsgnrTelno?: string;
dsgnrOgdpNm?: string;
dsgnrEmlAddr?: string;
atchFileGroupId?: string;
prgrsSttsComCd?: string;
}
/** 백엔드 FnrsCnsltnMngSaveRequestDto */
export interface FnrsCnsltnMngSaveRequestDto {
fnrsCnsltnDocNo?: string;
admdstCd?: string;
envoSeComCd?: string;
taskSeComCd?: string;
cfmtnBgtBizNo?: string;
bgtAplyYr?: string;
bizNm?: string;
bizBgngYr?: string;
bizEndYr?: string;
bizCn?: string;
aplcntNm?: string;
aplcntMblTelno?: string;
aplcntOgdpNm?: string;
aplcntEmlAddr?: string;
dsgnrNm?: string;
dsgnrTelno?: string;
dsgnrOgdpNm?: string;
dsgnrEmlAddr?: string;
atchFileGroupId?: string;
prgrsSttsComCd?: string;
}
/** 백엔드 FnrsCnsltnMngUpdateRequestDto */
export interface FnrsCnsltnMngUpdateRequestDto {
admdstCd?: string;
envoSeComCd?: string;
taskSeComCd?: string;
cfmtnBgtBizNo?: string;
bgtAplyYr?: string;
bizNm?: string;
bizBgngYr?: string;
bizEndYr?: string;
bizCn?: string;
aplcntNm?: string;
aplcntMblTelno?: string;
aplcntOgdpNm?: string;
aplcntEmlAddr?: string;
dsgnrNm?: string;
dsgnrTelno?: string;
dsgnrOgdpNm?: string;
dsgnrEmlAddr?: string;
atchFileGroupId?: string;
prgrsSttsComCd?: string;
}
/** 백엔드 FnrsCnsltnMngDetResponseDto */
export interface FnrsCnsltnMngDetResponseDto {
detDto?: FnrsCnsltnMngResponseDto;
bizSclList?: BizSclTreeDto[];
bizCstList?: BizCstTreeDto[];
cklstResultList?: CklstRvwRsltTreeDto[];
esntlFileList?: EsntlAtchFileMngTreeResponseDto[];
etcFileList?: AtflMngClientResponseDto[];
rvwList?: FnrsCnsltnRvwOpnnResponseDto[];
splmntDmndList?: FnrsCnsltnSplmntDmndResponseDto[];
elaprDocPrgrsList?: ElaprDocPrgrsClientResponseDto[];
}
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
export interface FnrsCnsltnApplySaveRequestDto {
techReViewSaveCd?: string;
rvwStpCd?: string;
fnrsCnsltnMng: FnrsCnsltnMngSaveRequestDto;
bzScaleList?: BzScaleItem[];
bizCstList?: BizCstItem[];
cklstResultList?: CklstResultItem[];
cklstResultMap?: Record<string, CklstResultItem[]>;
esntlAtchFileList?: EsntlAtchFileItem[];
rvwOpnnList?: FnrsCnsltnRvwOpnnSaveRequestDto[];
splmntDmndList?: FnrsCnsltnSplmntDmndItem[];
}
/** 백엔드 DocPrgrsUpdateRequestDto */
export interface DocPrgrsUpdateRequestDto {
docNo?: string;
prgrsSttsComCd?: string;
}
export interface BzScaleItem {
cnstpSeCd?: string;
bscPl?: number;
budgetReflct?: number;
thtiDsgn?: number;
}
export interface BizCstItem {
acntgSeCd?: string;
bscPlanAmt?: number;
bgtRfltAmt?: number;
tstmDesignAmt?: number;
}
export interface CklstResultItem {
cklstRvwSn?: number;
cklstArtclSn?: number;
cklstSn?: number;
rvwStpComCd: string;
selected?: string;
picRvwCn?: string;
}
export interface EsntlAtchFileItem {
esntlAtchFileId?: string;
atchFileGroupId?: string;
}
export interface FnrsCnsltnSplmntDmndItem {
splmntDmndSn?: number;
splmntArtclComCd?: string;
splmntDmndCn?: string;
splmntDmndDt?: string;
splmntDmndAtchFileGroupId?: string;
splmntCn?: string;
splmntPicNm?: string;
splmntCmptnDt?: string;
splmntAtchFileGroupId?: string;
cmptnYn?: string;
}
export interface BizSclTreeDto {
cnstpSeCd?: string;
cnstpSeNm?: string;
bscPl?: number;
budgetReflct?: number;
thtiDsgn?: number;
}
export interface BizCstTreeDto {
acntgSeCd?: string;
acntgSeNm?: string;
bscPlanAmt?: number;
bgtRfltAmt?: number;
tstmDesignAmt?: number;
}
export interface CklstRvwRsltTreeDto {
cklstRvwSn?: number;
cklstArtclSn?: number;
cklstSn?: number;
rvwStpComCd?: string;
selected?: string;
picRvwCn?: string;
cklstNm?: string;
cklstArtclNm?: string;
}
export interface EsntlAtchFileMngTreeResponseDto {
esntlAtchFileId?: string;
atchFileGroupId?: string;
esntlAtchFileNm?: string;
esntlAtchYn?: string;
}
export interface AtflMngClientResponseDto {
atflId?: string;
orgnlFileNm?: string;
atflFlieSz?: number;
atflType?: string;
dwnldNocs?: number;
regDt?: string;
}
export interface FnrsCnsltnRvwOpnnResponseDto {
fnrsCnsltnRvwOpnnSn?: number;
fnrsCnsltnDocNo?: string;
rvwStpComCd?: string;
rvwOpnn?: string;
rvwOpnnDt?: string;
rgtr?: string;
rgtrNm?: string;
}
export interface FnrsCnsltnRvwOpnnSaveRequestDto {
fnrsCnsltnDocNo?: string;
rvwStpComCd?: string;
rvwOpnn?: string;
}
export interface FnrsCnsltnSplmntDmndResponseDto {
splmntDmndSn?: number;
fnrsCnsltnDocNo?: string;
splmntArtclComCd?: string;
splmntDmndCn?: string;
splmntDmndDt?: string;
splmntDmndAtchFileGroupId?: string;
splmntCn?: string;
splmntPicNm?: string;
splmntCmptnDt?: string;
splmntAtchFileGroupId?: string;
cmptnYn?: string;
}
export interface ElaprDocPrgrsClientResponseDto {
elaprDmndNo?: string;
elaprAplnNo?: string;
docNo?: string;
docPrgrsSn?: number;
prgrsSttsComCd?: string;
prgrsSttsCdNm?: string;
aprvrNm?: string;
aprvDt?: string;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface FnrsCnsltnMngService {
findPage(searchDto: FnrsCnsltnMngSearchDto): Promise<PageResponse<FnrsCnsltnMngResponseDto>>;
findById(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnMngDetResponseDto>;
save(body: FnrsCnsltnMngSaveRequestDto): Promise<string>;
saveApply(body: FnrsCnsltnApplySaveRequestDto): Promise<string>;
update(fnrsCnsltnDocNo: string, body: FnrsCnsltnMngUpdateRequestDto): Promise<string>;
techReview(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<void>;
submitSupplement(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<string>;
submitReqSupplement(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<string>;
remove(fnrsCnsltnDocNo: string): Promise<void>;
changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void>;
}
@@ -1,111 +0,0 @@
"use client";
import { Fragment } from "react";
import { Badge } from "@common/components";
interface ProgressStepIndicatorProps {
prgrsSttsComCd: string;
prgrsSttsCdNm: string;
}
const MAIN_STEP_LABELS = {
save: "저장",
apply: "신청완료",
techReview: "기술검토",
reply: "검토결과 회신",
} as const;
const TECH_REVIEW_STATUS_CODES = new Set(["LE", "ER", "EE", "TE", "TS", "TR", "TP"]);
type BadgeState = "completed" | "current" | "pending" | "action";
interface ProgressBadge {
label: string;
state: BadgeState;
}
function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] {
const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview;
const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply;
if (prgrsSttsComCd === "SV") {
return [
{ label: MAIN_STEP_LABELS.save, state: "current" },
{ label: MAIN_STEP_LABELS.apply, state: "pending" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "ST") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "current" },
{ label: MAIN_STEP_LABELS.techReview, state: "pending" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: techReviewLabel, state: "action" },
{ label: MAIN_STEP_LABELS.reply, state: "pending" },
];
}
if (prgrsSttsComCd === "EP") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: replyLabel, state: "action" },
];
}
if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") {
return [
{ label: MAIN_STEP_LABELS.save, state: "completed" },
{ label: MAIN_STEP_LABELS.apply, state: "completed" },
{ label: MAIN_STEP_LABELS.techReview, state: "completed" },
{ label: MAIN_STEP_LABELS.reply, state: "completed" },
];
}
return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }];
}
function getBadgeColor(state: BadgeState) {
switch (state) {
case "completed":
return "green";
case "current":
return "blue";
case "pending":
return "lightGray";
case "action":
return "orange";
}
}
function ProgressStepIndicator({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepIndicatorProps) {
const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm);
return (
<div className="cnfm-card">
<h3 className="text-[1.5rem] font-semibold">진행 단계</h3>
<div className="cnfm-step-wrap mt-3">
{badges.map((badge, index) => (
<Fragment key={badge.label}>
{index > 0 && <span className="cnfm-step-separator">&gt;</span>}
<Badge color={getBadgeColor(badge.state)}>{badge.label}</Badge>
</Fragment>
))}
</div>
</div>
);
}
export { ProgressStepIndicator };
+1
View File
@@ -15,6 +15,7 @@ export const ENDPOINT = {
CKLST_ARTCL: "/as-service/api/v1/cklst-artcl",
CKLST_RVW: "/as-service/api/v1/cklst-rvw",
ADSTDSTR: "/as-service/api/v1/adstdstr",
FNRS_CNSLTN_MNG: "/as-service/api/v1/fnrs-cnsltn-mng",
},
USER_SERVICE: {
LOGIN: "/user-service/login",