Merge branch 'main' into feature/0612_sentry_fr_merge

# Conflicts:
#	apps/as/src/pages/instl-sncn/ui/cnfm-view-page.tsx
#	pnpm-lock.yaml
This commit is contained in:
2026-07-08 16:47:33 +09:00
736 changed files with 63742 additions and 12242 deletions
+1
View File
@@ -29,4 +29,5 @@ settings.json
.agent
apps/**/tsconfig.tsbuildinfo
packages/**/tsconfig.tsbuildinfo
logs/
@@ -1 +0,0 @@
export { AcntgCodeManagementPage as default } from "@/pages/acntg-code-management";
@@ -1 +0,0 @@
export { CnstpCodeManagementPage as default } from "@/pages/cnstp-code-management";
@@ -0,0 +1 @@
export { CodeManagementPage as default } from "@/pages/code-management";
@@ -0,0 +1,10 @@
import { FnrsEditPage } from "@/pages/fnrs-cnsltn";
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <FnrsEditPage id={id} />;
}
@@ -0,0 +1,10 @@
import { FnrsViewPage } from "@/pages/fnrs-cnsltn";
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: PageProps) {
const { id } = await params;
return <FnrsViewPage id={id} />;
}
@@ -0,0 +1,3 @@
import { FnrsListPage } from "@/pages/fnrs-cnsltn";
export default FnrsListPage;
@@ -0,0 +1,12 @@
import { FnrsRegistPage } from "@/pages/fnrs-cnsltn";
import { parseTaskSeComCdFromApi } from "@/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema";
interface PageProps {
searchParams: Promise<{ taskSeComCd?: string }>;
}
export default async function Page({ searchParams }: PageProps) {
const params = await searchParams;
const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd);
return <FnrsRegistPage taskSeComCd={taskSeComCd.length ? taskSeComCd : undefined} />;
}
@@ -0,0 +1,10 @@
import { InstlEditPage } from "@/pages/instl-sncn";
interface InstlSncnEditRouteProps {
params: Promise<{ id: string }>;
}
export default async function Page({ params }: InstlSncnEditRouteProps) {
const { id } = await params;
return <InstlEditPage id={id} />;
}
@@ -1,10 +0,0 @@
import { CnfmModifyPage } from "@/pages/instl-sncn";
export default async function InstlSncnModifyPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <CnfmModifyPage id={id} />;
}
@@ -1,10 +1,6 @@
import { CnfmViewPage } from "@/pages/instl-sncn";
export default async function InstlSncnViewPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return <CnfmViewPage id={id} />;
}
import { InstlViewPage } from "@/pages/instl-sncn";
export default async function InstlSncnDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <InstlViewPage id={id} />;
}
@@ -1,3 +1,3 @@
import { CnfmListPage } from "@/pages/instl-sncn";
export default CnfmListPage;
import { InstlListPage } from "@/pages/instl-sncn";
export default InstlListPage;
@@ -0,0 +1,14 @@
import { InstlRegistPage } from "@/pages/instl-sncn";
interface InstlSncnRegistRouteProps {
searchParams: Promise<{ taskSeComCd?: string }>;
}
export default async function Page({ searchParams }: InstlSncnRegistRouteProps) {
const params = await searchParams;
const taskSeComCd = params.taskSeComCd
? params.taskSeComCd.split(",").filter(Boolean)
: undefined;
return <InstlRegistPage taskSeComCd={taskSeComCd} />;
}
@@ -1,5 +0,0 @@
import { CnfmRegistPage } from "@/pages/instl-sncn";
export default function Page(props: any) {
return <CnfmRegistPage {...props} />;
}
+18
View File
@@ -0,0 +1,18 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import {
ACCESS_TOKEN,
AUTH_USER_ID,
CLAIM_NAME,
REFRESH_TOKEN,
} from "@/shared/config";
export async function POST() {
const cookieStore = await cookies();
cookieStore.delete(ACCESS_TOKEN);
cookieStore.delete(REFRESH_TOKEN);
cookieStore.delete(AUTH_USER_ID);
cookieStore.delete(CLAIM_NAME);
return NextResponse.json({ message: "Logged out" }, { status: 200 });
}
+1
View File
@@ -31,6 +31,7 @@
"@tanstack/react-query-devtools": "^5.75.5",
"@tanstack/react-table": "^8.21.2",
"axios": "^1.8.1",
"class-variance-authority": "^0.7.1",
"dayjs": "^1.11.13",
"js-cookie": "^3.0.5",
"lucide-react": "^0.474.0",
+2
View File
@@ -7,6 +7,7 @@ import { useQueryClient } from "@tanstack/react-query";
import { useMenuStore } from "@/shared/lib";
import { PageLayout } from "@/shared/ui/page-layout";
import { authService } from "@/entities/auth";
import type { MenuItem } from "@/entities/menu";
import { BaseLayout } from "@common/components";
@@ -32,6 +33,7 @@ export function BaseLayoutWrapper({ children }: BaseLayoutWrapperProps) {
return (
<BaseLayout
queryClient={queryClient}
authService={authService}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
@@ -12,7 +12,7 @@ import {
type AcntgSeMngTreeDto,
} from "../model/acntg-interface";
const BASE = ENDPOINT.AS_COMMON_SERVICE.ACNTG_SE;
const BASE = ENDPOINT.AS_SERVICE.ACNTG_SE;
async function findAll(params: AcntgSeMngSearchDto): Promise<PageResponse<AcntgSeMngResponseDto>> {
const response = await client.get<PageResponse<AcntgSeMngResponseDto>>(BASE, { params });
+1 -1
View File
@@ -10,7 +10,7 @@ import type {
import { acntgSeMngService } from "./acntg-service";
const acntgSeMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.ACNTG_SE] as const;
const acntgSeMngBaseKey = [ENDPOINT.AS_SERVICE.ACNTG_SE] as const;
export function useAcntgSeMngPage(params: AcntgSeMngSearchDto) {
return useQuery({
@@ -16,6 +16,7 @@ export interface AcntgSeMngResponseDto {
cdLvNo: number;
useYn: string;
expln?: string;
sortSeq?: number;
rgtr?: string;
rgtrNm?: string;
mdfr?: string;
@@ -39,6 +40,7 @@ export interface AcntgSeMngSaveRequestDto {
cdLvNo: number;
useYn?: string;
expln?: string;
sortSeq?: number;
}
/** PUT /{acntgSeCd} 요청 (mode는 서비스에서 U로 설정) */
@@ -0,0 +1,26 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
CmCdAdstDstrComboResponseDto,
CmCdAdstDstrService,
} from "../model/adstdstr-interface";
const BASE = ENDPOINT.AS_SERVICE.ADSTDSTR;
async function findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]> {
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/sido`);
return response.data;
}
async function findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/gugun`, {
params: { sido },
});
return response.data;
}
export const cmCdAdstDstrService = {
findSidoCombo,
findGugunCombo,
} satisfies CmCdAdstDstrService;
@@ -0,0 +1,22 @@
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { cmCdAdstDstrService } from "./adstdstr-service";
const cmCdAdstDstrBaseKey = [ENDPOINT.AS_SERVICE.ADSTDSTR] as const;
export function useFindSidoCombo() {
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findSidoCombo"],
queryFn: () => cmCdAdstDstrService.findSidoCombo(),
});
}
export function useFindGugunCombo(sido: string) {
return useQuery({
queryKey: [...cmCdAdstDstrBaseKey, "findGugunCombo", sido],
queryFn: () => cmCdAdstDstrService.findGugunCombo(sido),
enabled: Boolean(sido),
});
}
+6
View File
@@ -0,0 +1,6 @@
export { cmCdAdstDstrService } from "./api/adstdstr-service";
export { useFindGugunCombo, useFindSidoCombo } from "./api/queries";
export type {
CmCdAdstDstrComboResponseDto,
CmCdAdstDstrService,
} from "./model/adstdstr-interface";
@@ -0,0 +1,11 @@
/** 백엔드 CmCdAdstDstrComboResponseDto */
export interface CmCdAdstDstrComboResponseDto {
adstDstrCd: string;
adstDstrNm: string;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface CmCdAdstDstrService {
findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
}
@@ -0,0 +1,22 @@
import { AxiosResponse } from "axios";
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { AttachService } from "../model/attach-interface";
async function deleteAttachmentByAtflId(atflId: string): Promise<void> {
if (!atflId) return;
await client.delete(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`);
}
async function downloadAttachmentByAtflCd(atflCd: string): Promise<AxiosResponse> {
return await client.get<Blob>(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD}/${atflCd}`, {
responseType: "blob",
});
}
export const attachService = {
deleteAttachmentByAtflId,
downloadAttachmentByAtflCd,
} satisfies AttachService;
@@ -0,0 +1,39 @@
import { useMutation } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { parseFilenameFromResponse } from "../lib/parse-filename-from-response";
import { attachService } from "./attach-service";
const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const;
const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const;
export function useDeleteAttachment() {
return useMutation({
mutationKey: [...attachBaseKey, "delete"],
mutationFn: (atflId: string) => attachService.deleteAttachmentByAtflId(atflId),
});
}
export function useDownloadAttachmentByAtflCd() {
return useMutation({
mutationKey: [...attachDownloadBaseKey, "download"],
mutationFn: async (atflCd: string) => {
const response = await attachService.downloadAttachmentByAtflCd(atflCd);
const filename = parseFilenameFromResponse(response);
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(link);
},
});
}
+3
View File
@@ -0,0 +1,3 @@
export { attachService } from "./api/attach-service";
export { useDeleteAttachment, useDownloadAttachmentByAtflCd } from "./api/queries";
export type { Attach, AttachService } from "./model/attach-interface";
@@ -0,0 +1,11 @@
import type { AxiosResponse } from "axios";
export function parseFilenameFromResponse(response: AxiosResponse): string {
const contentDisposition = response.headers["content-disposition"];
if (!contentDisposition) return "download";
const filenameMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/);
if (!filenameMatch?.[1]) return "download";
return decodeURIComponent(filenameMatch[1]);
}
@@ -0,0 +1,22 @@
import { AxiosResponse } from "axios";
/** 백엔드 AtflMngResponseDto */
export interface Attach {
atflCd: string;
atflSn: number;
atflId: string;
physFileNm: string;
orgnlFileNm: string;
atflFlieSz: number;
atflType: string;
dwnldNocs: number;
delYn: string;
lnkgDmnId: string;
lnkgDmnNm: string;
regDt: string;
}
export interface AttachService {
deleteAttachmentByAtflId: (atflId: string) => Promise<void>;
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
}
+29 -4
View File
@@ -1,8 +1,19 @@
import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
import { getRemainingMs } from "@common/auth";
import {
ACCESS_TOKEN,
AUTH_LOGIN_REDIRECT_URL,
AUTH_USER_ID,
CLAIM_NAME,
ENDPOINT,
} from "@/shared/config";
import { type AuthPayload, type AuthService, getRemainingMs } from "@common/auth";
import { clearAuthToken, setToken, setupRefreshTimer } from "../lib/token-manager";
import type { AuthPayload, AuthService } from "../model/auth-interface";
import {
clearAuthToken,
clearRefreshTimer,
removeToken,
setToken,
setupRefreshTimer,
} from "../lib/token-manager";
async function login(payload: AuthPayload): Promise<void> {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
@@ -66,14 +77,28 @@ async function silentRefresh(): Promise<void> {
}
async function logout(): Promise<void> {
try {
await fetch("/api/auth/logout", {
method: "POST",
credentials: "include",
});
} catch (error) {
console.error("Logout API failed:", error);
}
clearAuthToken();
removeToken("site-id-key");
removeToken("site-theme-key");
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
window.location.href = AUTH_LOGIN_REDIRECT_URL;
}
}
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend: clearRefreshTimer,
logout,
};
@@ -8,6 +8,7 @@ export const {
setToken,
removeToken,
setupRefreshTimer,
clearRefreshTimer,
clearAuthToken,
getAccessToken,
getRefreshToken,
@@ -1,10 +0,0 @@
export interface AuthPayload {
userId: string;
password: string;
}
export interface AuthService {
login(payload: AuthPayload): Promise<void>;
silentRefresh(): Promise<void>;
logout(): Promise<void>;
}
@@ -0,0 +1,47 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import { buildBnsSearchDto } from "../lib/build-bns-search-dto";
import type {
BnsDetailResponseWrapperDto,
BnsIfService,
BnsListResponseWrapperDto,
BnsSearchDto,
EmCodeListResponseDto,
} from "../model/bns-if-interface";
const BASE = ENDPOINT.AS_SERVICE.BNS_IF;
async function getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto> {
const response = await client.get<BnsListResponseWrapperDto>(`${BASE}/bns`, {
params: buildBnsSearchDto(params),
});
return response.data;
}
async function getBnsDetail(
gbn: string,
yr: string,
seqNo: string,
): Promise<BnsDetailResponseWrapperDto> {
const response = await client.get<BnsDetailResponseWrapperDto>(`${BASE}/bns/detail`, {
params: { gbn, yr, seqNo },
});
return response.data;
}
async function getEmCodeList(
dpmCd?: string,
lclgvrnmntCd?: string,
): Promise<EmCodeListResponseDto> {
const response = await client.get<EmCodeListResponseDto>(`${BASE}/emCodeList`, {
params: { dpmCd, lclgvrnmntCd },
});
return response.data;
}
export const bnsIfService = {
getBnsList,
getBnsDetail,
getEmCodeList,
} satisfies BnsIfService;
@@ -0,0 +1,33 @@
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type { BnsSearchDto } from "../model/bns-if-interface";
import { bnsIfService } from "./bns-if-service";
const bnsIfBaseKey = [ENDPOINT.AS_SERVICE.BNS_IF] as const;
export function useBnsList(params: BnsSearchDto, options?: { enabled?: boolean }) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getBnsList", params],
queryFn: () => bnsIfService.getBnsList(params),
enabled: options?.enabled,
placeholderData: keepPreviousData,
});
}
export function useBnsDetail(gbn: string, yr: string, seqNo: string) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getBnsDetail", gbn, yr, seqNo],
queryFn: () => bnsIfService.getBnsDetail(gbn, yr, seqNo),
enabled: Boolean(gbn && yr && seqNo),
});
}
export function useEmCodeList(dpmCd?: string, lclgvrnmntCd?: string) {
return useQuery({
queryKey: [...bnsIfBaseKey, "getEmCodeList", dpmCd, lclgvrnmntCd],
queryFn: () => bnsIfService.getEmCodeList(dpmCd, lclgvrnmntCd),
});
}
+13
View File
@@ -0,0 +1,13 @@
export { bnsIfService } from "./api/bns-if-service";
export { useBnsDetail, useBnsList, useEmCodeList } from "./api/queries";
export { buildBnsSearchDto } from "./lib/build-bns-search-dto";
export type {
BnsDetailResponseDto,
BnsDetailResponseWrapperDto,
BnsIfService,
BnsListResponseDto,
BnsListResponseWrapperDto,
BnsSearchDto,
EmCodeItemDto,
EmCodeListResponseDto,
} from "./model/bns-if-interface";
@@ -0,0 +1,20 @@
import type { BnsSearchDto } from "../model/bns-if-interface";
export function buildBnsSearchDto(input: {
cPage: number;
searchStartYear?: string;
searchSido?: string;
searchGugun?: string;
}): BnsSearchDto {
const dto: BnsSearchDto = { cPage: input.cPage };
const year = input.searchStartYear?.trim();
const sido = input.searchSido?.trim();
const gugun = input.searchGugun?.trim();
if (year) dto.searchStartYear = year;
if (sido) dto.searchSido = sido;
if (gugun) dto.searchGugun = gugun;
return dto;
}
@@ -0,0 +1,125 @@
/** 백엔드 BnsSearchDto */
export interface BnsSearchDto {
searchStartYear?: string;
cPage: number;
searchSido?: string;
searchGugun?: string;
}
/** 백엔드 BnsListResponseWrapperDto */
export interface BnsListResponseWrapperDto {
list: BnsListResponseDto[];
cnt: number;
}
/** 백엔드 BnsListResponseDto */
export interface BnsListResponseDto {
cPage: number;
gbn: number;
chk: number;
seqNo: string;
rgnNm: string;
sido: string;
gugun: string;
bzCd: string;
bzNm: string;
yr: string;
strtYr: string;
edYr: string;
totBzamt: string;
}
/** 백엔드 BnsDetailResponseWrapperDto */
export interface BnsDetailResponseWrapperDto {
saup: unknown;
detail: BnsDetailResponseDto;
}
/** 백엔드 BnsDetailResponseDto */
export interface BnsDetailResponseDto {
cPage: number;
seqNo: string;
confBzCd: string;
chngDgr: string;
yr: string;
sido: string;
gugun: string;
rvbsnNm: string;
lgbsnNm: string;
mdmbsnNm: string;
lclgvrnmntNm: string;
accKdNm: string;
bzKdNm: string;
untBzNm: string;
dtlBzNm: string;
mevBzNm: string;
ddtlBzNm: string;
strtYr: string;
edYr: string;
aidRt: string;
bzStatus: string;
bzCd: string;
bzNm: string;
bzLoc: string;
efflWqltStndrdNm: string;
goalYear: string;
bssProm: string;
uppPlan: string;
techInsp: string;
bssCont: string;
rmk: string;
inflBscFctNm: string;
inflBscFctCd: string;
trtMtd: string;
swrExtns: string;
swrChng: string;
swrRnvt: string;
totFamNum: string;
pumpCnt: string;
rvWbsn: string;
rvText: string;
imprvExpctFlw: string;
imprvExpctWqlt: string;
trtplntPlEfflWqlt: string;
wqltAvg: string;
wqltHgh: string;
wqltLow: string;
stdCost: string;
bzEtc: string;
bscFctCd: string;
bscFctNm: string;
fctCpc: string;
fctCpcAfr: string;
trtDstrArea: string;
trtDstrAreaAfr: string;
swrTrtPopl: string;
swrTrtPoplAfr: string;
adstPopl: string;
}
/** 백엔드 EmCodeItemDto */
export interface EmCodeItemDto {
code: string;
data: string;
upCode: string;
}
/** 백엔드 EmCodeListResponseDto */
export interface EmCodeListResponseDto {
rvbsnList: EmCodeItemDto[];
lgbsnList: EmCodeItemDto[];
mdmbsnList: EmCodeItemDto[];
accList: EmCodeItemDto[];
bzList: EmCodeItemDto[];
untList: EmCodeItemDto[];
dtlList: EmCodeItemDto[];
mevList: EmCodeItemDto[];
ddtlList: EmCodeItemDto[];
}
/** Controller public 메서드명·시그니처와 동일 */
export interface BnsIfService {
getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto>;
getBnsDetail(gbn: string, yr: string, seqNo: string): Promise<BnsDetailResponseWrapperDto>;
getEmCodeList(dpmCd?: string, lclgvrnmntCd?: string): Promise<EmCodeListResponseDto>;
}
@@ -11,33 +11,25 @@ import type {
CklstArtclUpdateRequest,
} from "../model/cklst-artcl-interface";
const BASE = ENDPOINT.AS_SERVICE.CKLST_ARTCL;
async function findPage(params: CklstArtclSearchParams): Promise<PageResponse<CklstArtcl>> {
const response = await client.get<PageResponse<CklstArtcl>>(
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL,
{ params },
);
const response = await client.get<PageResponse<CklstArtcl>>(BASE, { params });
return response.data;
}
async function findByCklstSn(cklstSn: number): Promise<CklstArtcl[]> {
const response = await client.get<CklstArtcl[]>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/by-cklst/${cklstSn}`,
);
const response = await client.get<CklstArtcl[]>(`${BASE}/by-cklst/${cklstSn}`);
return response.data;
}
async function findById(cklstArtclSn: number, cklstSn: number): Promise<CklstArtcl> {
const response = await client.get<CklstArtcl>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/${cklstArtclSn}/${cklstSn}`,
);
const response = await client.get<CklstArtcl>(`${BASE}/${cklstArtclSn}/${cklstSn}`);
return response.data;
}
async function create(body: CklstArtclSaveRequest): Promise<CklstArtclId> {
const response = await client.post<CklstArtclId>(
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL,
body,
);
const response = await client.post<CklstArtclId>(BASE, body);
return response.data;
}
@@ -46,17 +38,12 @@ async function update(
cklstSn: number,
body: CklstArtclUpdateRequest,
): Promise<CklstArtclId> {
const response = await client.put<CklstArtclId>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/${cklstArtclSn}/${cklstSn}`,
body,
);
const response = await client.put<CklstArtclId>(`${BASE}/${cklstArtclSn}/${cklstSn}`, body);
return response.data;
}
async function remove(cklstArtclSn: number, cklstSn: number): Promise<CklstArtclId> {
const response = await client.delete<CklstArtclId>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/${cklstArtclSn}/${cklstSn}`,
);
const response = await client.delete<CklstArtclId>(`${BASE}/${cklstArtclSn}/${cklstSn}`);
return response.data;
}
@@ -11,40 +11,39 @@ import type {
CklstTreeSearchParams,
} from "../model/cklst-mng-interface";
const BASE = ENDPOINT.AS_SERVICE.CKLST;
async function findPage(params: CklstMngSearchParams): Promise<PageResponse<CklstMng>> {
const response = await client.get<PageResponse<CklstMng>>(ENDPOINT.AS_CHECKLIST_SERVICE.CKLST, {
const response = await client.get<PageResponse<CklstMng>>(BASE, {
params,
});
return response.data;
}
async function findById(cklstSn: number): Promise<CklstMng> {
const response = await client.get<CklstMng>(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/${cklstSn}`);
const response = await client.get<CklstMng>(`${BASE}/${cklstSn}`);
return response.data;
}
async function findTree(params: CklstTreeSearchParams): Promise<CklstTreeNode[]> {
const response = await client.get<CklstTreeNode[]>(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/list/tree`, {
const response = await client.get<CklstTreeNode[]>(`${BASE}/list/tree`, {
params,
});
return response.data;
}
async function create(body: CklstMngSaveRequest): Promise<number> {
const response = await client.post<number>(ENDPOINT.AS_CHECKLIST_SERVICE.CKLST, body);
const response = await client.post<number>(BASE, body);
return response.data;
}
async function update(cklstSn: number, body: CklstMngSaveRequest): Promise<number> {
const response = await client.put<number>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/${cklstSn}`,
body,
);
const response = await client.put<number>(`${BASE}/${cklstSn}`, body);
return response.data;
}
async function remove(cklstSn: number): Promise<number> {
const response = await client.delete<number>(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/${cklstSn}`);
const response = await client.delete<number>(`${BASE}/${cklstSn}`);
return response.data;
}
@@ -11,18 +11,15 @@ import type {
CklstRvwUpdateRequest,
} from "../model/cklst-rvw-interface";
const BASE = ENDPOINT.AS_SERVICE.CKLST_RVW;
async function findPage(params: CklstRvwSearchParams): Promise<PageResponse<CklstRvw>> {
const response = await client.get<PageResponse<CklstRvw>>(
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW,
{ params },
);
const response = await client.get<PageResponse<CklstRvw>>(BASE, { params });
return response.data;
}
async function findByArtcl(cklstSn: number, cklstArtclSn: number): Promise<CklstRvw[]> {
const response = await client.get<CklstRvw[]>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/by-artcl/${cklstSn}/${cklstArtclSn}`,
);
const response = await client.get<CklstRvw[]>(`${BASE}/by-artcl/${cklstSn}/${cklstArtclSn}`);
return response.data;
}
@@ -31,17 +28,12 @@ async function findById(
cklstArtclSn: number,
cklstSn: number,
): Promise<CklstRvw> {
const response = await client.get<CklstRvw>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
);
const response = await client.get<CklstRvw>(`${BASE}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`);
return response.data;
}
async function create(body: CklstRvwSaveRequest): Promise<CklstRvwId> {
const response = await client.post<CklstRvwId>(
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW,
body,
);
const response = await client.post<CklstRvwId>(BASE, body);
return response.data;
}
@@ -52,7 +44,7 @@ async function update(
body: CklstRvwUpdateRequest,
): Promise<CklstRvwId> {
const response = await client.put<CklstRvwId>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
`${BASE}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
body,
);
return response.data;
@@ -64,7 +56,7 @@ async function remove(
cklstSn: number,
): Promise<CklstRvwId> {
const response = await client.delete<CklstRvwId>(
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
`${BASE}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
);
return response.data;
}
+3 -3
View File
@@ -18,9 +18,9 @@ import { cklstArtclService } from "./cklst-artcl-service";
import { cklstMngService } from "./cklst-mng-service";
import { cklstRvwService } from "./cklst-rvw-service";
const cklstMngBaseKey = [ENDPOINT.AS_CHECKLIST_SERVICE.CKLST] as const;
const cklstArtclBaseKey = [ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL] as const;
const cklstRvwBaseKey = [ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW] as const;
const cklstMngBaseKey = [ENDPOINT.AS_SERVICE.CKLST] as const;
const cklstArtclBaseKey = [ENDPOINT.AS_SERVICE.CKLST_ARTCL] as const;
const cklstRvwBaseKey = [ENDPOINT.AS_SERVICE.CKLST_RVW] as const;
export function useCklstMngPage(params: CklstMngSearchParams) {
return useQuery({
@@ -12,7 +12,7 @@ import {
type CnstpSeMngTreeDto,
} from "../model/cnstp-interface";
const BASE = ENDPOINT.AS_COMMON_SERVICE.CNSTP_SE;
const BASE = ENDPOINT.AS_SERVICE.CNSTP_SE;
async function findAll(params: CnstpSeMngSearchDto): Promise<PageResponse<CnstpSeMngResponseDto>> {
const response = await client.get<PageResponse<CnstpSeMngResponseDto>>(BASE, { params });
+1 -1
View File
@@ -10,7 +10,7 @@ import type {
import { cnstpSeMngService } from "./cnstp-service";
const cnstpSeMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.CNSTP_SE] as const;
const cnstpSeMngBaseKey = [ENDPOINT.AS_SERVICE.CNSTP_SE] as const;
export function useCnstpSeMngPage(params: CnstpSeMngSearchDto) {
return useQuery({
@@ -16,6 +16,7 @@ export interface CnstpSeMngResponseDto {
cdLvNo: number;
useYn: string;
expln?: string;
sortSeq?: number;
rgtr?: string;
rgtrNm?: string;
mdfr?: string;
@@ -39,6 +40,7 @@ export interface CnstpSeMngSaveRequestDto {
cdLvNo?: number;
useYn?: string;
expln?: string;
sortSeq?: number;
}
/** PUT /{cnstpSeCd} 요청 (mode는 서비스에서 U로 설정) */
@@ -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;
@@ -1,7 +0,0 @@
import { client } from "@/shared/api";
export async function processElaprPrgrs(elaprPrgrsId: string, atrzPrgrsSttsComCd = "ATRZ_STTS_003"): Promise<void> {
await client.put(`/as-elapr-service/api/v1/elapr-prgrs-stts/${elaprPrgrsId}/process`, {
atrzPrgrsSttsComCd,
});
}
@@ -0,0 +1,102 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { PageResponse } from "@/shared/model";
import type {
ElaprApproverChangeRequestDto,
ElaprApproverInsertRequestDto,
ElaprPrgrsSttsMyDocSearchParams,
ElaprPrgrsSttsResponseDto,
ElaprPrgrsSttsSaveRequestDto,
ElaprPrgrsSttsService,
ElaprProcessRequestDto,
} from "../model/elapr-prgrs-stts-interface";
const BASE = ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS;
async function findAllByDmndId(elaprDmndId?: string): Promise<ElaprPrgrsSttsResponseDto[]> {
const response = await client.get<ElaprPrgrsSttsResponseDto[]>(BASE, {
params: elaprDmndId ? { elaprDmndId } : undefined,
});
return response.data;
}
async function findById(elaprPrgrsId: string): Promise<ElaprPrgrsSttsResponseDto> {
const response = await client.get<ElaprPrgrsSttsResponseDto>(`${BASE}/${elaprPrgrsId}`);
return response.data;
}
async function save(body: ElaprPrgrsSttsSaveRequestDto): Promise<ElaprPrgrsSttsResponseDto> {
const response = await client.post<ElaprPrgrsSttsResponseDto>(BASE, body);
return response.data;
}
async function process(
elaprPrgrsId: string,
body: ElaprProcessRequestDto,
): Promise<ElaprPrgrsSttsResponseDto> {
const response = await client.put<ElaprPrgrsSttsResponseDto>(
`${BASE}/${elaprPrgrsId}/process`,
body,
);
return response.data;
}
async function processByDocNoAndAprvrUuid(
elaprDocNo: string,
aprvrUuid: string,
body: ElaprProcessRequestDto,
): Promise<ElaprPrgrsSttsResponseDto> {
const response = await client.put<ElaprPrgrsSttsResponseDto>(
`${BASE}/doc/${elaprDocNo}/aprvr/${aprvrUuid}/process`,
body,
);
return response.data;
}
async function deletePrgrsStts(elaprPrgrsId: string): Promise<void> {
await client.delete(`${BASE}/${elaprPrgrsId}`);
}
async function updateApprover(
elaprPrgrsId: string,
body: ElaprApproverChangeRequestDto,
): Promise<ElaprPrgrsSttsResponseDto> {
const response = await client.put<ElaprPrgrsSttsResponseDto>(
`${BASE}/${elaprPrgrsId}/approver`,
body,
);
return response.data;
}
async function insertApprover(
elaprDmndId: string,
body: ElaprApproverInsertRequestDto,
): Promise<ElaprPrgrsSttsResponseDto> {
const response = await client.post<ElaprPrgrsSttsResponseDto>(
`${BASE}/${elaprDmndId}/approver/insert`,
body,
);
return response.data;
}
async function findMyElaperDocList(
params: ElaprPrgrsSttsMyDocSearchParams,
): Promise<PageResponse<ElaprPrgrsSttsResponseDto>> {
const response = await client.get<PageResponse<ElaprPrgrsSttsResponseDto>>(`${BASE}/my/doc`, {
params,
});
return response.data;
}
export const elaprPrgrsSttsService = {
findAllByDmndId,
findById,
save,
process,
processByDocNoAndAprvrUuid,
delete: deletePrgrsStts,
updateApprover,
insertApprover,
findMyElaperDocList,
} satisfies ElaprPrgrsSttsService;
+217 -32
View File
@@ -8,6 +8,14 @@ import type {
ElaprAplnSaveRequest,
ElaprAplnUpdateRequest,
} from "../model/elapr-apln-interface";
import type { ElaprDmndSaveRequestDto, ElaprDmndSearchDto } from "../model/elapr-dmnd-interface";
import type {
ElaprApproverChangeRequestDto,
ElaprApproverInsertRequestDto,
ElaprPrgrsSttsMyDocSearchParams,
ElaprPrgrsSttsSaveRequestDto,
ElaprProcessRequestDto,
} from "../model/elapr-prgrs-stts-interface";
import type {
ElaprTmpltSaveRequest,
ElaprTmpltSearchParams,
@@ -15,10 +23,19 @@ import type {
} from "../model/elapr-tmplt-interface";
import { elaprAplnService } from "./elapr-apln-service";
import { elaprDmndService } from "./elapr-dmnd-service";
import { elaprPrgrsSttsService } from "./elapr-prgrs-stts-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;
const elaprPrgrsSttsBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS] as const;
function invalidateElaprPrgrsSttsQueries(queryClient: ReturnType<typeof useQueryClient>) {
queryClient.invalidateQueries({ queryKey: elaprPrgrsSttsBaseKey });
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
}
export function useElaprTmpltPage(params: ElaprTmpltSearchParams) {
return useQuery({
@@ -50,13 +67,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 +111,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 +126,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 +144,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 +159,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 +169,193 @@ 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 });
},
});
}
export function useElaprPrgrsSttsByDmndId(elaprDmndId: string | null | undefined) {
return useQuery({
queryKey: [...elaprPrgrsSttsBaseKey, "findAllByDmndId", elaprDmndId],
queryFn: () => elaprPrgrsSttsService.findAllByDmndId(elaprDmndId!),
enabled: Boolean(elaprDmndId),
});
}
export function useElaprPrgrsStts(elaprPrgrsId: string | null | undefined) {
return useQuery({
queryKey: [...elaprPrgrsSttsBaseKey, elaprPrgrsId],
queryFn: () => elaprPrgrsSttsService.findById(elaprPrgrsId!),
enabled: Boolean(elaprPrgrsId),
});
}
export function useElaprPrgrsSttsMyDocPage(params: ElaprPrgrsSttsMyDocSearchParams) {
return useQuery({
queryKey: [...elaprPrgrsSttsBaseKey, "findMyElaperDocList", params],
queryFn: () => elaprPrgrsSttsService.findMyElaperDocList(params),
});
}
export function useSaveElaprPrgrsStts() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: ElaprPrgrsSttsSaveRequestDto) => elaprPrgrsSttsService.save(body),
onSuccess: () => {
invalidateElaprPrgrsSttsQueries(queryClient);
},
});
}
export function useProcessElaprPrgrsStts() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
elaprPrgrsId,
body,
}: {
elaprPrgrsId: string;
body: ElaprProcessRequestDto;
}) => elaprPrgrsSttsService.process(elaprPrgrsId, body),
onSuccess: () => {
invalidateElaprPrgrsSttsQueries(queryClient);
},
});
}
export function useProcessElaprPrgrsSttsByDoc() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
elaprDocNo,
aprvrUuid,
body,
}: {
elaprDocNo: string;
aprvrUuid: string;
body: ElaprProcessRequestDto;
}) => elaprPrgrsSttsService.processByDocNoAndAprvrUuid(elaprDocNo, aprvrUuid, body),
onSuccess: () => {
invalidateElaprPrgrsSttsQueries(queryClient);
},
});
}
export function useDeleteElaprPrgrsStts() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (elaprPrgrsId: string) => elaprPrgrsSttsService.delete(elaprPrgrsId),
onSuccess: () => {
invalidateElaprPrgrsSttsQueries(queryClient);
},
});
}
export function useUpdateElaprPrgrsSttsApprover() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
elaprPrgrsId,
body,
}: {
elaprPrgrsId: string;
body: ElaprApproverChangeRequestDto;
}) => elaprPrgrsSttsService.updateApprover(elaprPrgrsId, body),
onSuccess: () => {
invalidateElaprPrgrsSttsQueries(queryClient);
},
});
}
export function useInsertElaprPrgrsSttsApprover() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
elaprDmndId,
body,
}: {
elaprDmndId: string;
body: ElaprApproverInsertRequestDto;
}) => elaprPrgrsSttsService.insertApprover(elaprDmndId, body),
onSuccess: () => {
invalidateElaprPrgrsSttsQueries(queryClient);
},
});
}
+91 -33
View File
@@ -1,33 +1,91 @@
export { elaprAplnService } from "./api/elapr-apln-service";
export { elaprTmpltService } from "./api/elapr-tmplt-service";
export {
useCreateElaprTmplt,
useDeleteElaprApln,
useDeleteElaprTmplt,
useElaprApln,
useElaprAplnPage,
useElaprTmplt,
useElaprTmpltPage,
useReorderElaprApln,
useSaveMultiElaprApln,
useUpdateElaprApln,
useUpdateElaprTmplt,
} from "./api/queries";
export {
ELAPR_APLN_SAVE_MODE,
type ElaprApln,
type ElaprAplnListParams,
type ElaprAplnReorderItem,
type ElaprAplnReorderRequest,
type ElaprAplnSaveMode,
type ElaprAplnSaveRequest,
type ElaprAplnService,
type ElaprAplnUpdateRequest,
} from "./model/elapr-apln-interface";
export type {
ElaprTmplt,
ElaprTmpltSaveRequest,
ElaprTmpltSearchParams,
ElaprTmpltService,
ElaprTmpltUpdateRequest,
} from "./model/elapr-tmplt-interface";
export { elaprAplnService } from "./api/elapr-apln-service";
export { elaprDmndService } from "./api/elapr-dmnd-service";
export { elaprPrgrsSttsService } from "./api/elapr-prgrs-stts-service";
export { elaprTmpltService } from "./api/elapr-tmplt-service";
export {
useCreateElaprTmplt,
useDeleteElaprApln,
useDeleteElaprPrgrsStts,
useDeleteElaprTmplt,
useElaprApln,
useElaprAplnPage,
useElaprDmnd,
useElaprDmndPage,
useElaprDocPrgrsByDocNo,
useElaprPrgrsStts,
useElaprPrgrsSttsByDmndId,
useElaprPrgrsSttsMyDocPage,
useElaprTmplt,
useElaprTmpltPage,
useInsertElaprPrgrsSttsApprover,
useProcessElaprPrgrsStts,
useProcessElaprPrgrsSttsByDoc,
useRemoveElaprDmnd,
useReorderElaprApln,
useSaveElaprDmnd,
useSaveElaprPrgrsStts,
useSaveMultiElaprApln,
useUpdateElaprApln,
useUpdateElaprDmnd,
useUpdateElaprDmndStatus,
useUpdateElaprPrgrsSttsApprover,
useUpdateElaprTmplt,
} from "./api/queries";
export {
assignAtrzSeqNumbers,
buildSaveMultiPayload,
hasUnsavedChanges,
} from "./lib/apln-save-utils";
export {
getVisibleRows,
validateAllRowsForSave,
validateAplnRow,
validateAtrzOrder,
validateUniqueSeqNo,
} from "./lib/apln-validators";
export {
buildElaprPrgrsSttsListFromApln,
buildElaprPrgrsSttsListFromAplnRows,
} from "./lib/dmnd-save-utils";
export {
findCurrentUserPendingApprovalRequest,
hasCurrentUserPendingApprovalRequest,
} from "./lib/approval-request-utils";
export { createDraftRowKey, useAplnRows } from "./lib/use-apln-rows";
export { useDndSensors } from "./lib/use-dnd-sensors";
export {
ELAPR_APLN_SAVE_MODE,
type ElaprApln,
type ElaprAplnListParams,
type ElaprAplnReorderItem,
type ElaprAplnReorderRequest,
type ElaprAplnSaveMode,
type ElaprAplnSaveRequest,
type ElaprAplnService,
type ElaprAplnUpdateRequest,
} from "./model/elapr-apln-interface";
export type { ElaprAplnRow } from "./model/elapr-apln-row-types";
export type {
ElaprDmndResponseDto,
ElaprDmndSaveRequestDto,
ElaprDmndSearchDto,
ElaprDmndService,
ElaprDocPrgrsResponseDto,
} from "./model/elapr-dmnd-interface";
export type {
ElaprApproverChangeRequestDto,
ElaprApproverInsertRequestDto,
ElaprPrgrsSttsMyDocSearchParams,
ElaprPrgrsSttsResponseDto,
ElaprPrgrsSttsSaveRequestDto,
ElaprPrgrsSttsSearchDto,
ElaprPrgrsSttsService,
ElaprProcessRequestDto,
} from "./model/elapr-prgrs-stts-interface";
export type {
ElaprTmplt,
ElaprTmpltSaveRequest,
ElaprTmpltSearchParams,
ElaprTmpltService,
ElaprTmpltUpdateRequest,
} from "./model/elapr-tmplt-interface";
@@ -1,8 +1,16 @@
import { ELAPR_APLN_SAVE_MODE, type ElaprApln, type ElaprAplnSaveRequest } from "@/entities/elapr";
import {
ELAPR_APLN_SAVE_MODE,
type ElaprApln,
type ElaprAplnSaveRequest,
} from "../model/elapr-apln-interface";
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
import { assignAtrzSeqNumbers } from "./assign-atrz-seq";
import { getVisibleRows } from "./validators";
import type { ElaprAplnRow } from "../model/apln-row-types";
import { getVisibleRows } from "./apln-validators";
/** 그리드 행 순서(1-based)로 결재순서번호 일괄 채번 */
export function assignAtrzSeqNumbers(rows: ElaprAplnRow[]): ElaprAplnRow[] {
return rows.map((row, index) => ({ ...row, atrzSeqNo: index + 1 }));
}
function rowDiffersFromServer(row: ElaprAplnRow, serverRow: ElaprApln): boolean {
return (
@@ -22,6 +30,25 @@ function shouldIncludeUpdate(row: ElaprAplnRow, serverRows: ElaprApln[]): boolea
return rowDiffersFromServer(row, serverRow);
}
export function hasUnsavedChanges(rows: ElaprAplnRow[], serverRows: ElaprApln[]): boolean {
if (rows.some((row) => row.isDeleted && row.aplnId)) return true;
if (rows.some((row) => row.isNew && !row.isDeleted)) return true;
const visibleWithSeq = assignAtrzSeqNumbers(getVisibleRows(rows));
for (const row of visibleWithSeq) {
if (row.isNew) continue;
if (!row.aplnId) continue;
if (row.isDirty) return true;
const serverRow = serverRows.find((s) => s.aplnId === row.aplnId);
if (!serverRow) continue;
if (rowDiffersFromServer(row, serverRow)) return true;
}
return false;
}
export function buildSaveMultiPayload(
rows: ElaprAplnRow[],
elaprTmpltId: string,
@@ -1,4 +1,4 @@
import type { ElaprAplnRow } from "../model/apln-row-types";
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
const ATRZ_RECEIVER_CD = "ATRZ01";
const ATRZ_FINAL_CD = "ATRZ99";
@@ -0,0 +1,25 @@
import type { ElaprDocPrgrsResponseDto } from "../model/elapr-dmnd-interface";
export function hasCurrentUserPendingApprovalRequest(
list: ElaprDocPrgrsResponseDto[],
currentUserUuid: string | undefined,
): boolean {
if (!currentUserUuid) return false;
return list.some(
(row) =>
row.aprvrUuid === currentUserUuid && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002",
);
}
export function findCurrentUserPendingApprovalRequest(
list: ElaprDocPrgrsResponseDto[],
currentUserUuid: string | undefined,
): ElaprDocPrgrsResponseDto | undefined {
if (!currentUserUuid) return undefined;
return list.find(
(row) =>
row.aprvrUuid === currentUserUuid && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002",
);
}
@@ -0,0 +1,43 @@
import type { ElaprApln } from "../model/elapr-apln-interface";
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
import type { ElaprPrgrsSttsSaveRequestDto } from "../model/elapr-prgrs-stts-interface";
import { getVisibleRows } from "./apln-validators";
function toPrgrsSttsItem(
aprvrUuid: string | undefined,
atrzSeComCd: string | undefined,
atrzSeqNo: number | undefined,
): ElaprPrgrsSttsSaveRequestDto {
if (!aprvrUuid?.trim()) {
throw new Error("결재자가 지정되지 않은 행이 있습니다.");
}
if (!atrzSeComCd?.trim()) {
throw new Error("결재구분이 지정되지 않은 행이 있습니다.");
}
if (atrzSeqNo == null) {
throw new Error("결재순서번호가 지정되지 않은 행이 있습니다.");
}
return {
aprvrUuid,
atrzSeComCd,
atrzSeqNo,
};
}
/** 서식 결재선 API 응답 → 기안 요청용 진행상태 목록 */
export function buildElaprPrgrsSttsListFromApln(
items: ElaprApln[],
): ElaprPrgrsSttsSaveRequestDto[] {
return items.map((item) => toPrgrsSttsItem(item.aprvrUuid, item.atrzSeComCd, item.atrzSeqNo));
}
/** 결재선 그리드 행 → 기안 요청용 진행상태 목록 (서버가 seq 기준 상태 자동 결정) */
export function buildElaprPrgrsSttsListFromAplnRows(
rows: ElaprAplnRow[],
): ElaprPrgrsSttsSaveRequestDto[] {
return getVisibleRows(rows).map((row) =>
toPrgrsSttsItem(row.aprvrUuid, row.atrzSeComCd, row.atrzSeqNo),
);
}
@@ -4,12 +4,11 @@ import { useCallback, useEffect, useState } from "react";
import { arrayMove } from "@dnd-kit/sortable";
import type { ElaprApln } from "@/entities/elapr";
import type { ElaprApln } from "../model/elapr-apln-interface";
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
import type { ElaprAplnRow } from "../model/apln-row-types";
import { assignAtrzSeqNumbers } from "./assign-atrz-seq";
import { getVisibleRows } from "./validators";
import { assignAtrzSeqNumbers } from "./apln-save-utils";
import { getVisibleRows } from "./apln-validators";
function toRow(item: ElaprApln): ElaprAplnRow {
return {
@@ -101,7 +100,13 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
return prev.map((row) => {
if (row.rowKey !== editingRowKey) return row;
if (row.snapshot) {
const { snapshot: _s, isNew: _n, isDirty: _d, isDeleted: _del, ...restored } = row.snapshot;
const {
snapshot: _s,
isNew: _n,
isDirty: _d,
isDeleted: _del,
...restored
} = row.snapshot;
return { ...restored, isNew: false, isDirty: false, isDeleted: false };
}
return { ...row, isDirty: false };
@@ -110,19 +115,16 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
setEditingRowKey(null);
}, [editingRowKey]);
const commitEdit = useCallback(
(rowKey: string) => {
setRows((prev) =>
prev.map((row) => {
if (row.rowKey !== rowKey) return row;
const { snapshot: _s, ...committed } = row;
return { ...committed, isDirty: true };
}),
);
setEditingRowKey(null);
},
[],
);
const commitEdit = useCallback((rowKey: string) => {
setRows((prev) =>
prev.map((row) => {
if (row.rowKey !== rowKey) return row;
const { snapshot: _s, ...committed } = row;
return { ...committed, isDirty: true };
}),
);
setEditingRowKey(null);
}, []);
const markDeleted = useCallback((rowKey: string) => {
setRows((prev) => {
@@ -158,7 +160,10 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
const visibleKeys = new Set(reorderedVisible.map((r) => r.rowKey));
const deletedRows = prev.filter((r) => r.isDeleted);
const merged = [...reorderedVisible, ...deletedRows.filter((r) => !visibleKeys.has(r.rowKey))];
const merged = [
...reorderedVisible,
...deletedRows.filter((r) => !visibleKeys.has(r.rowKey)),
];
payloadRows = reorderedVisible;
return merged;
@@ -1,3 +1,5 @@
"use client";
import { KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
@@ -1,4 +1,4 @@
import type { ElaprApln } from "@/entities/elapr";
import type { ElaprApln } from "./elapr-apln-interface";
/** 그리드 로컬 행 — API 응답 + UI 보조 필드 */
export interface ElaprAplnRow extends Omit<ElaprApln, "aplnId"> {
@@ -1,22 +1,63 @@
export interface ElaprDmndSaveRequest {
import type { PageableParams, PageResponse } from "@/shared/model";
import type { ElaprPrgrsSttsSaveRequestDto } from "./elapr-prgrs-stts-interface";
/** 백엔드 ElaprDmndSaveRequestDto */
export interface ElaprDmndSaveRequestDto {
elaprDocNo: string;
elaprTmpltId: string;
dmndTtl: string;
dmndCn: string;
elaprPrgrsSttsList?: ElaprPrgrsSttsSaveRequestDto[];
}
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[]>;
}
@@ -0,0 +1,78 @@
import type { PageableParams, PageResponse } from "@/shared/model";
/** 백엔드 ElaprPrgrsSttsResponseDto */
export interface ElaprPrgrsSttsResponseDto {
elaprPrgrsId: string;
elaprDmndId?: string;
aprvrUuid?: string;
atrzSeComCd?: string;
atrzSeComNm?: string;
atrzSeqNo?: number;
atrzPrgrsSttsComCd?: string;
atrzPrgrsSttsComNm?: string;
rjctRsn?: string;
atrzDt?: string;
}
/** 백엔드 ElaprPrgrsSttsSaveRequestDto */
export interface ElaprPrgrsSttsSaveRequestDto {
elaprDmndId?: string;
aprvrUuid: string;
atrzSeComCd: string;
atrzSeqNo: number;
atrzPrgrsSttsComCd?: string;
}
/** 백엔드 ElaprPrgrsSttsSearchDto */
export interface ElaprPrgrsSttsSearchDto {
atrzSeComCd: string;
atrzSeqNo: number;
atrzPrgrsSttsComCd?: string;
}
/** 백엔드 ElaprPrgrsSttsSearchDto + Pageable */
export interface ElaprPrgrsSttsMyDocSearchParams extends ElaprPrgrsSttsSearchDto, PageableParams {}
/** 백엔드 ElaprProcessRequestDto */
export interface ElaprProcessRequestDto {
atrzPrgrsSttsComCd: string;
rjctRsn?: string;
finalApprovalYn?: boolean;
}
/** 백엔드 ElaprApproverChangeRequestDto */
export interface ElaprApproverChangeRequestDto {
aprvrUuid: string;
}
/** 백엔드 ElaprApproverInsertRequestDto */
export interface ElaprApproverInsertRequestDto {
aprvrUuid: string;
atrzSeComCd: string;
atrzSeqNo: number;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface ElaprPrgrsSttsService {
findAllByDmndId(elaprDmndId?: string): Promise<ElaprPrgrsSttsResponseDto[]>;
findById(elaprPrgrsId: string): Promise<ElaprPrgrsSttsResponseDto>;
save(body: ElaprPrgrsSttsSaveRequestDto): Promise<ElaprPrgrsSttsResponseDto>;
process(elaprPrgrsId: string, body: ElaprProcessRequestDto): Promise<ElaprPrgrsSttsResponseDto>;
processByDocNoAndAprvrUuid(
elaprDocNo: string,
aprvrUuid: string,
body: ElaprProcessRequestDto,
): Promise<ElaprPrgrsSttsResponseDto>;
delete(elaprPrgrsId: string): Promise<void>;
updateApprover(
elaprPrgrsId: string,
body: ElaprApproverChangeRequestDto,
): Promise<ElaprPrgrsSttsResponseDto>;
insertApprover(
elaprDmndId: string,
body: ElaprApproverInsertRequestDto,
): Promise<ElaprPrgrsSttsResponseDto>;
findMyElaperDocList(
params: ElaprPrgrsSttsMyDocSearchParams,
): Promise<PageResponse<ElaprPrgrsSttsResponseDto>>;
}
+10 -19
View File
@@ -11,50 +11,41 @@ import type {
EsntlAtchFileMngTreeSearchParams,
} from "../model/esntl-interface";
const BASE = ENDPOINT.AS_SERVICE.ESNTL_ATCH_FILE;
async function findTree(
params: EsntlAtchFileMngTreeSearchParams,
): Promise<EsntlAtchFileMngTreeResponseDto[]> {
const response = await client.get<EsntlAtchFileMngTreeResponseDto[]>(
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/list/tree`,
{ params },
);
const response = await client.get<EsntlAtchFileMngTreeResponseDto[]>(`${BASE}/list/tree`, {
params,
});
return response.data;
}
async function findPage(
params: EsntlAtchFileMngSearchParams,
): Promise<PageResponse<EsntlAtchFileMng>> {
const response = await client.get<PageResponse<EsntlAtchFileMng>>(
ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE,
{ params },
);
const response = await client.get<PageResponse<EsntlAtchFileMng>>(BASE, { params });
return response.data;
}
async function findById(esntlAtchFileId: string): Promise<EsntlAtchFileMng> {
const response = await client.get<EsntlAtchFileMng>(
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/${esntlAtchFileId}`,
);
const response = await client.get<EsntlAtchFileMng>(`${BASE}/${esntlAtchFileId}`);
return response.data;
}
async function create(body: EsntlAtchFileMngSaveRequest): Promise<string> {
const response = await client.post<string>(ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE, body);
const response = await client.post<string>(BASE, body);
return response.data;
}
async function update(esntlAtchFileId: string, body: EsntlAtchFileMngSaveRequest): Promise<string> {
const response = await client.put<string>(
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/${esntlAtchFileId}`,
body,
);
const response = await client.put<string>(`${BASE}/${esntlAtchFileId}`, body);
return response.data;
}
async function remove(esntlAtchFileId: string): Promise<string> {
const response = await client.delete<string>(
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/${esntlAtchFileId}`,
);
const response = await client.delete<string>(`${BASE}/${esntlAtchFileId}`);
return response.data;
}
+1 -1
View File
@@ -10,7 +10,7 @@ import type {
import { esntlAtchFileMngService } from "./esntl-service";
const esntlAtchFileMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE] as const;
const esntlAtchFileMngBaseKey = [ENDPOINT.AS_SERVICE.ESNTL_ATCH_FILE] as const;
export function useEsntlAtchFileMngTree(params: EsntlAtchFileMngTreeSearchParams) {
return useQuery({
@@ -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";
export 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 });
},
});
}
+39
View File
@@ -0,0 +1,39 @@
export { fnrsCnsltnMngService } from "./api/fnrs-cnsltn-service";
export { fnrsCnsltnMngBaseKey } from "./api/queries";
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,
FnrsSharItem,
} from "./model/fnrs-cnsltn-interface";
@@ -0,0 +1,177 @@
import type {
AtflMngClientResponseDto,
BizCstSaveBase,
BzScaleSaveBase,
CklstResultSaveBase,
DocPrgrsUpdateRequestDto,
EsntlAtchFileSaveBase,
PageableParams,
PageResponse,
PbadmsApplySaveHeader,
PbadmsMngDetResponseBase,
RvwOpnnResponseExtension,
RvwOpnnSaveBase,
SplmntDmndResponseExtension,
SplmntDmndSaveBase,
} from "@/shared/model";
import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se";
export type {
AtflMngClientResponseDto,
BizCstTreeDto,
BizSclTreeDto,
CklstRvwRsltTreeDto,
DocPrgrsUpdateRequestDto,
ElaprDocPrgrsClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
} from "@/shared/model";
/** 백엔드 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;
sharAtchFileGroupId?: string;
prgrsSttsComCd?: string;
rtlztnFcltSeComCd?: string;
rtlztnUsgSeComCd?: string;
}
/** 백엔드 FnrsCnsltnMngUpdateRequestDto */
export type FnrsCnsltnMngUpdateRequestDto = Omit<FnrsCnsltnMngSaveRequestDto, "fnrsCnsltnDocNo">;
/** 백엔드 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;
sharAtchFileGroupId?: string;
prgrsSttsComCd?: string;
}
interface FnrsCnsltnMngResponseExtension {
fnrsCnsltnDocNo: string;
envoSeCdNm?: string;
taskSeCdNm?: string;
prgrsSttsCdNm?: string;
regDt?: string;
rgtr?: string;
rgtrNm?: string;
mdfcnDt?: string;
sido?: string;
gugun?: string;
}
/** 백엔드 FnrsCnsltnMngResponseDto */
export type FnrsCnsltnMngResponseDto = Omit<FnrsCnsltnMngSaveRequestDto, "fnrsCnsltnDocNo"> &
FnrsCnsltnMngResponseExtension;
export type BzScaleItem = BzScaleSaveBase;
export type BizCstItem = BizCstSaveBase;
export interface CklstResultItem extends CklstResultSaveBase {
selected?: string;
}
export type EsntlAtchFileItem = EsntlAtchFileSaveBase;
export type FnrsCnsltnSplmntDmndItem = SplmntDmndSaveBase;
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */
export interface FnrsSharItem {
fnrsSharSeCd?: string;
exstnAmt?: number;
chgRqtAmt?: number;
ajmtAmt?: number;
rmrkCn?: string;
envoRmrkCn?: string;
}
/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */
export interface FnrsCnsltnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase {
fnrsCnsltnDocNo: string;
rvwOpnnSn: number;
}
/** 백엔드 FnrsCnsltnRvwOpnnResponseDto */
export type FnrsCnsltnRvwOpnnResponseDto = Partial<
Pick<FnrsCnsltnRvwOpnnSaveRequestDto, "fnrsCnsltnDocNo" | "rvwOpnnSn">
> &
Omit<FnrsCnsltnRvwOpnnSaveRequestDto, "fnrsCnsltnDocNo" | "rvwOpnnSn"> &
RvwOpnnResponseExtension;
/** 백엔드 FnrsCnsltnSplmntDmndResponseDto */
export type FnrsCnsltnSplmntDmndResponseDto = FnrsCnsltnSplmntDmndItem &
SplmntDmndResponseExtension & {
fnrsCnsltnDocNo?: string;
};
/** 백엔드 FnrsCnsltnMngDetResponseDto */
export type FnrsCnsltnMngDetResponseDto = PbadmsMngDetResponseBase<
FnrsCnsltnMngResponseDto,
FnrsCnsltnRvwOpnnResponseDto,
FnrsCnsltnSplmntDmndResponseDto
> & {
fnrsSharList?: FnrsSharBizCstTreeDto[];
sharAtchFileList?: AtflMngClientResponseDto[];
};
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
export interface FnrsCnsltnApplySaveRequestDto extends PbadmsApplySaveHeader {
fnrsCnsltnMng: FnrsCnsltnMngSaveRequestDto;
bzScaleList?: BzScaleItem[];
bizCstList?: BizCstItem[];
cklstResultList?: CklstResultItem[];
cklstResultMap?: Record<string, CklstResultItem[]>;
esntlAtchFileList?: EsntlAtchFileItem[];
rvwOpnnList?: FnrsCnsltnRvwOpnnSaveRequestDto[];
splmntDmndList?: FnrsCnsltnSplmntDmndItem[];
fnrsSharList?: FnrsSharItem[];
}
/** 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>;
}
@@ -0,0 +1,70 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { PageResponse } from "@/shared/model";
import {
FNRS_SHAR_SE_SAVE_MODE,
type FnrsSharBizCstTreeDto,
type FnrsSharSeMngResponseDto,
type FnrsSharSeMngSaveRequestDto,
type FnrsSharSeMngSearchDto,
type FnrsSharSeMngService,
type FnrsSharSeMngTreeDto,
} from "../model/fnrs-shar-se-interface";
const BASE = ENDPOINT.AS_SERVICE.FNRS_SHAR_SE;
async function findAll(
params: FnrsSharSeMngSearchDto,
): Promise<PageResponse<FnrsSharSeMngResponseDto>> {
const response = await client.get<PageResponse<FnrsSharSeMngResponseDto>>(BASE, { params });
return response.data;
}
async function findTree(): Promise<FnrsSharSeMngTreeDto[]> {
const response = await client.get<FnrsSharSeMngTreeDto[]>(`${BASE}/list/tree`);
return response.data;
}
async function findById(fnrsSharSeCd: string): Promise<FnrsSharSeMngResponseDto> {
const response = await client.get<FnrsSharSeMngResponseDto>(`${BASE}/${fnrsSharSeCd}`);
return response.data;
}
async function saveMulti(
body: FnrsSharSeMngSaveRequestDto[],
): Promise<FnrsSharSeMngResponseDto[]> {
const response = await client.post<FnrsSharSeMngResponseDto[]>(BASE, body);
return response.data;
}
async function update(fnrsSharSeCd: string, body: FnrsSharSeMngSaveRequestDto): Promise<string> {
const response = await client.put<string>(`${BASE}/${fnrsSharSeCd}`, {
...body,
fnrsSharSeCd,
mode: FNRS_SHAR_SE_SAVE_MODE.UPDATE,
});
return response.data;
}
async function deleteFnrsSharSeCd(fnrsSharSeCd: string): Promise<string> {
const response = await client.delete<string>(`${BASE}/${fnrsSharSeCd}`);
return response.data;
}
async function findTreeFnrsShar(fnrsCnsltnDocNo: string): Promise<FnrsSharBizCstTreeDto[]> {
const response = await client.get<FnrsSharBizCstTreeDto[]>(
`${BASE}/list/tree/fnrs-shar/${fnrsCnsltnDocNo}`,
);
return response.data;
}
export const fnrsSharSeMngService = {
findAll,
findTree,
findById,
saveMulti,
update,
delete: deleteFnrsSharSeCd,
findTreeFnrsShar,
} satisfies FnrsSharSeMngService;
@@ -0,0 +1,85 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type {
FnrsSharSeMngSaveRequestDto,
FnrsSharSeMngSearchDto,
} from "../model/fnrs-shar-se-interface";
import { fnrsSharSeMngService } from "./fnrs-shar-se-service";
const fnrsSharSeMngBaseKey = [ENDPOINT.AS_SERVICE.FNRS_SHAR_SE] as const;
export function useFnrsSharSeMngPage(params: FnrsSharSeMngSearchDto) {
return useQuery({
queryKey: [...fnrsSharSeMngBaseKey, "findAll", params],
queryFn: () => fnrsSharSeMngService.findAll(params),
});
}
export function useFnrsSharSeMngTree() {
return useQuery({
queryKey: [...fnrsSharSeMngBaseKey, "findTree"],
queryFn: () => fnrsSharSeMngService.findTree(),
staleTime: Infinity,
});
}
export function useFnrsSharSeMng(fnrsSharSeCd: string | null | undefined) {
return useQuery({
queryKey: [...fnrsSharSeMngBaseKey, "findById", fnrsSharSeCd],
queryFn: () => fnrsSharSeMngService.findById(fnrsSharSeCd!),
enabled: Boolean(fnrsSharSeCd),
});
}
export function useSaveMultiFnrsSharSeMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (body: FnrsSharSeMngSaveRequestDto[]) => fnrsSharSeMngService.saveMulti(body),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: fnrsSharSeMngBaseKey });
},
});
}
export function useUpdateFnrsSharSeMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
fnrsSharSeCd,
body,
}: {
fnrsSharSeCd: string;
body: FnrsSharSeMngSaveRequestDto;
}) => fnrsSharSeMngService.update(fnrsSharSeCd, body),
onSuccess: (_data, { fnrsSharSeCd }) => {
queryClient.invalidateQueries({ queryKey: fnrsSharSeMngBaseKey });
queryClient.invalidateQueries({
queryKey: [...fnrsSharSeMngBaseKey, "findById", fnrsSharSeCd],
});
},
});
}
export function useDeleteFnrsSharSeMng() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (fnrsSharSeCd: string) => fnrsSharSeMngService.delete(fnrsSharSeCd),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: fnrsSharSeMngBaseKey });
},
});
}
export function useFnrsSharBizCstTree(fnrsCnsltnDocNo: string | null | undefined) {
return useQuery({
queryKey: [...fnrsSharSeMngBaseKey, "findTreeFnrsShar", fnrsCnsltnDocNo],
queryFn: () => fnrsSharSeMngService.findTreeFnrsShar(fnrsCnsltnDocNo!),
enabled: Boolean(fnrsCnsltnDocNo),
});
}
@@ -0,0 +1,20 @@
export { fnrsSharSeMngService } from "./api/fnrs-shar-se-service";
export {
useDeleteFnrsSharSeMng,
useFnrsSharBizCstTree,
useFnrsSharSeMng,
useFnrsSharSeMngPage,
useFnrsSharSeMngTree,
useSaveMultiFnrsSharSeMng,
useUpdateFnrsSharSeMng,
} from "./api/queries";
export type {
FnrsSharBizCstTreeDto,
FnrsSharSeMngResponseDto,
FnrsSharSeMngSaveRequestDto,
FnrsSharSeMngSearchDto,
FnrsSharSeMngService,
FnrsSharSeMngTreeDto,
FnrsSharSeSaveMode,
} from "./model/fnrs-shar-se-interface";
export { FNRS_SHAR_SE_SAVE_MODE } from "./model/fnrs-shar-se-interface";
@@ -0,0 +1,83 @@
import type { PageableParams, PageResponse } from "@/shared/model";
export const FNRS_SHAR_SE_SAVE_MODE = {
CREATE: "C",
UPDATE: "U",
DELETE: "D",
} as const;
export type FnrsSharSeSaveMode = (typeof FNRS_SHAR_SE_SAVE_MODE)[keyof typeof FNRS_SHAR_SE_SAVE_MODE];
/** 백엔드 FnrsSharSeMngResponseDto */
export interface FnrsSharSeMngResponseDto {
fnrsSharSeCd: string;
fnrsSharSeCdNm: string;
upFnrsSharSeCd?: string;
cdLvNo: number;
useYn: string;
expln?: string;
sortSeq?: number;
rgtr?: string;
rgtrNm?: string;
mdfr?: string;
regDt?: string;
mdfcnDt?: string;
}
/** 백엔드 FnrsSharSeMngSearchDto + Pageable */
export interface FnrsSharSeMngSearchDto extends PageableParams {
fnrsSharSeCd?: string;
fnrsSharSeCdNm?: string;
useYn?: string;
}
/** 백엔드 FnrsSharSeMngSaveRequestDto */
export interface FnrsSharSeMngSaveRequestDto {
fnrsSharSeCd: string;
mode: FnrsSharSeSaveMode;
fnrsSharSeCdNm: string;
upFnrsSharSeCd?: string;
cdLvNo: number;
useYn?: string;
expln?: string;
sortSeq?: number;
}
/** 백엔드 FnrsSharSeMngTreeDto */
export interface FnrsSharSeMngTreeDto {
indFnrsSharSeCdNm: string;
fnrsSharSeCd: string;
fnrsSharSeCdNm: string;
upFnrsSharSeCd?: string;
useYn: string;
treeLevel: number;
treePath: string;
sortPath: string;
}
/** 백엔드 FnrsSharBizCstTreeDto */
export interface FnrsSharBizCstTreeDto {
indFnrsSharSeCdNm: string;
fnrsSharSeCd: string;
fnrsSharSeCdNm: string;
useYn: string;
treeLevel: number;
treePath: string;
sortPath: string;
exstnAmt?: number;
chgRqtAmt?: number;
ajmtAmt?: number;
rmrkCn?: string;
envoRmrkCn?: string;
}
/** Controller public 메서드명·시그니처와 동일 */
export interface FnrsSharSeMngService {
findAll(params: FnrsSharSeMngSearchDto): Promise<PageResponse<FnrsSharSeMngResponseDto>>;
findTree(): Promise<FnrsSharSeMngTreeDto[]>;
findById(fnrsSharSeCd: string): Promise<FnrsSharSeMngResponseDto>;
saveMulti(body: FnrsSharSeMngSaveRequestDto[]): Promise<FnrsSharSeMngResponseDto[]>;
update(fnrsSharSeCd: string, body: FnrsSharSeMngSaveRequestDto): Promise<string>;
delete(fnrsSharSeCd: string): Promise<string>;
findTreeFnrsShar(fnrsCnsltnDocNo: string): Promise<FnrsSharBizCstTreeDto[]>;
}
@@ -1,41 +1,91 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type { PageResponse } from "@/shared/model";
import type {
AdstZoneResponseDto,
DocPrgrsUpdateRequestDto,
InstlSncnApplySaveRequestDto,
InstlSncnMng,
InstlSncnMngDetResponse,
InstlSncnMngResponseDto,
InstlSncnMngSaveRequestDto,
InstlSncnMngSearchParams,
InstlSncnMngService,
SpringPage,
InstlSncnMngDetResponse,
InstlSncnMngUpdateRequestDto,
} from "../model/instl-sncn-interface";
export class InstlSncnApiService implements InstlSncnMngService {
public async getPage(params: InstlSncnMngSearchParams): Promise<SpringPage<InstlSncnMng>> {
const response = await client.get<SpringPage<InstlSncnMng>>(ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN, {
params,
});
return response.data;
}
async function getSidoCombo(): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`,
);
public async getById(id: string): Promise<InstlSncnMngDetResponse> {
const response = await client.get<InstlSncnMngDetResponse>(`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/${id}`);
return response.data;
}
public async saveApply(payload: InstlSncnApplySaveRequestDto): Promise<string> {
const response = await client.post<string>(
`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/save-apply`,
payload,
);
return response.data;
}
public async techReview(payload: InstlSncnApplySaveRequestDto): Promise<string> {
const response = await client.post<string>(
`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/tech-review`,
payload,
);
return response.data;
}
return response.data;
}
export const InstlSncnApi = new InstlSncnApiService();
async function getSigunguCombo(sidoCd: string): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`,
{ params: { sidoCd } },
);
return response.data;
}
const BASE = ENDPOINT.AS_SERVICE.INSTL_SNCN;
async function getPage(
params: InstlSncnMngSearchParams,
): Promise<PageResponse<InstlSncnMngResponseDto>> {
const response = await client.get<PageResponse<InstlSncnMngResponseDto>>(BASE, {
params,
});
return response.data;
}
async function getById(id: string): Promise<InstlSncnMngDetResponse> {
const response = await client.get<InstlSncnMngDetResponse>(`${BASE}/${id}`);
return response.data;
}
async function save(payload: InstlSncnMngSaveRequestDto): Promise<string> {
const response = await client.post<string>(BASE, payload);
return response.data;
}
async function saveApply(payload: InstlSncnApplySaveRequestDto): Promise<string> {
const response = await client.post<string>(`${BASE}/save-apply`, payload);
return response.data;
}
async function update(id: string, payload: InstlSncnMngUpdateRequestDto): Promise<void> {
await client.put(`${BASE}/${id}`, payload);
}
async function techReview(id: string, payload: InstlSncnApplySaveRequestDto): Promise<void> {
await client.post(`${BASE}/${id}/tech-review`, payload);
}
async function remove(id: string): Promise<void> {
await client.delete(`${BASE}/${id}`);
}
async function changePrgrsSttsComCd(payload: DocPrgrsUpdateRequestDto): Promise<void> {
await client.post(`${BASE}/doc/updatePrgrsStts`, payload);
}
export const instlSncnService = {
getPage,
getById,
save,
saveApply,
update,
techReview,
remove,
changePrgrsSttsComCd,
} satisfies InstlSncnMngService;
// TODO: st-anls-mst service 로 분리
export const instlSncnRegionService = {
getSidoCombo,
getSigunguCombo,
};
@@ -0,0 +1,126 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type {
DocPrgrsUpdateRequestDto,
InstlSncnApplySaveRequestDto,
InstlSncnMngSaveRequestDto,
InstlSncnMngSearchParams,
InstlSncnMngUpdateRequestDto,
} from "../model/instl-sncn-interface";
import { instlSncnRegionService, instlSncnService } from "./instl-sncn-service";
export const instlSncnKeys = {
all: ["instl-sncn"] as const,
list: () => [...instlSncnKeys.all, "list"] as const,
detail: (id: string) => [...instlSncnKeys.all, "detail", id] as const,
sidoCombo: () => [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sido"] as const,
sigunguCombo: (sidoCd: string) =>
[ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sigungu", sidoCd] as const,
};
export function useInstlSncnList(params: InstlSncnMngSearchParams) {
return useQuery({
queryKey: [...instlSncnKeys.list(), params],
queryFn: () => instlSncnService.getPage(params),
});
}
export function useInstlSncnDetail(id: string) {
return useQuery({
queryKey: instlSncnKeys.detail(id),
queryFn: () => instlSncnService.getById(id),
enabled: Boolean(id),
});
}
export function useInstlSncnSidoCombo() {
return useQuery({
queryKey: instlSncnKeys.sidoCombo(),
queryFn: () => instlSncnRegionService.getSidoCombo(),
});
}
export function useInstlSncnSigunguCombo(sidoCd: string) {
return useQuery({
queryKey: instlSncnKeys.sigunguCombo(sidoCd),
queryFn: () => instlSncnRegionService.getSigunguCombo(sidoCd),
enabled: Boolean(sidoCd),
});
}
export function useInstlSncnSave() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (payload: InstlSncnMngSaveRequestDto) => instlSncnService.save(payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
},
});
}
export function useInstlSncnSaveApply() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (payload: InstlSncnApplySaveRequestDto) => instlSncnService.saveApply(payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
},
});
}
export function useInstlSncnUpdate() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, payload }: { id: string; payload: InstlSncnMngUpdateRequestDto }) =>
instlSncnService.update(id, payload),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
queryClient.invalidateQueries({ queryKey: instlSncnKeys.detail(variables.id) });
},
});
}
export function useInstlSncnTechReview() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, payload }: { id: string; payload: InstlSncnApplySaveRequestDto }) =>
instlSncnService.techReview(id, payload),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
queryClient.invalidateQueries({ queryKey: instlSncnKeys.detail(variables.id) });
},
});
}
export function useInstlSncnRemove() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id: string) => instlSncnService.remove(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
},
});
}
export function useInstlSncnChangePrgrsStts() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (payload: DocPrgrsUpdateRequestDto) =>
instlSncnService.changePrgrsSttsComCd(payload),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
queryClient.invalidateQueries({
queryKey: instlSncnKeys.detail(variables.docNo),
});
},
});
}
+25 -4
View File
@@ -1,12 +1,33 @@
export { InstlSncnApi } from "./api/instl-sncn-service";
export { instlSncnRegionService, instlSncnService } from "./api/instl-sncn-service";
export {
instlSncnKeys,
useInstlSncnDetail,
useInstlSncnList,
useInstlSncnSidoCombo,
useInstlSncnSigunguCombo,
useInstlSncnTechReview,
} from "./api/queries";
export {
type AdstZoneResponseDto,
type AtflMngClientResponseDto,
type BizCstTreeDto,
type BizSclTreeDto,
type CklstRvwRsltTreeDto,
type DocPrgrsUpdateRequestDto,
type ElaprDocPrgrsClientResponseDto,
type EsntlAtchFileMngTreeResponseDto,
INSTL_SNCN_REGISTER_SELECTION_KEY,
type InstlSncnApplySaveRequestDto,
type InstlSncnBizCstSaveRequestDto,
type InstlSncnBzScaleSaveDto,
type InstlSncnCklstResultSaveDto,
type InstlSncnMng,
type InstlSncnEsntlAtchFileSaveDto,
type InstlSncnMngDetResponse,
type InstlSncnMngResponseDto,
type InstlSncnMngSaveRequestDto,
type InstlSncnMngSearchParams,
type SpringPage,
type ElaprDocPrgrsClientResponseDto,
type InstlSncnRvwOpnnResponseDto,
type InstlSncnRvwOpnnSaveRequestDto,
type InstlSncnSplmntDmndSaveDto,
type ReviewStepCode,
} from "./model/instl-sncn-interface";
@@ -1,78 +1,149 @@
export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection";
import type {
AtflMngClientResponseDto,
BizCstSaveBase,
BizCstTreeDto,
BizSclTreeDto,
BzScaleSaveBase,
CklstResultSaveBase,
DocPrgrsUpdateRequestDto,
ElaprDocPrgrsClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
EsntlAtchFileSaveBase,
PageableParams,
PageResponse,
PbadmsApplySaveHeader,
PbadmsMngDetResponseBase,
RvwOpnnResponseExtension,
RvwOpnnSaveBase,
SplmntDmndSaveBase,
} from "@/shared/model";
export interface SpringPage<T> {
content: T[];
totalElements: number;
totalPages: number;
number: number;
size: number;
first: boolean;
last: boolean;
numberOfElements: number;
export type {
AtflMngClientResponseDto,
BizCstTreeDto,
BizSclTreeDto,
CklstRvwRsltTreeDto,
DocPrgrsUpdateRequestDto,
ElaprDocPrgrsClientResponseDto,
EsntlAtchFileMngTreeResponseDto,
} from "@/shared/model";
/** 행정구역(시도·시군구) 콤보 응답 DTO */
export interface AdstZoneResponseDto {
/** 행정구역 코드 */
adstCd: string;
/** 행정구역 명 */
adstNm: string;
}
export interface InstlSncnMngSearchParams {
export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection";
export interface InstlSncnMngSearchParams extends PageableParams {
bizNm?: string;
taskSeComCd?: string;
sidoComCd?: string;
sggComCd?: string;
page?: number;
size?: number;
sort?: string;
}
export interface InstlSncnMng {
gugun: string;
sido: string;
instlSncnDocNo: string;
admdstCd: string;
sidoNm?: string;
sggNm?: string;
chrgrNm?: string;
instNm?: string;
envoSeComCd: string;
/** 설치인가관리 저장 요청 DTO */
export interface InstlSncnMngSaveRequestDto {
/** 설치인가문서번호 */
instlSncnDocNo?: string;
/** 행정구역코드 */
admdstCd?: string;
/** 환경청구분공통코드 */
envoSeComCd?: string;
/** 인가구분공통코드 */
sncnSeComCd: string;
envoSeCdNm: string;
/** 업무구분공통코드 */
taskSeComCd: string;
taskSeCdNm: string;
bgtBizCd: string;
bgtAplyYr: string;
/** 예산사업코드 */
bgtBizCd?: string;
/** 예산신청연도 */
bgtAplyYr?: string;
/** 사업명 */
bizNm: string;
/** 사업시작연도 */
bizBgngYr: string;
/** 사업종료연도 */
bizEndYr: string;
/** 사업내용 */
bizCn: string;
aplcntNm: string;
aplcntMblTelno: string;
aplcntOgdpNm: string;
aplcntEmlAddr: string;
/** 신청자명 */
aplcntNm?: string;
/** 신청자휴대전화번호 */
aplcntMblTelno?: string;
/** 신청자소속명 */
aplcntOgdpNm?: string;
/** 신청자이메일주소 */
aplcntEmlAddr?: string;
/** 설계자명 */
dsgnrNm: string;
/** 설계자전화번호 */
dsgnrTelno: string;
/** 설계자소속명 */
dsgnrOgdpNm: string;
/** 설계자이메일주소 */
dsgnrEmlAddr: string;
atchFileGroupId: string;
prgrsSttsComCd: string;
/** 첨부파일그룹아이디 */
atchFileGroupId?: string;
/** 진행상태공통코드 */
prgrsSttsComCd?: string;
/** 재이용시설구분공통코드 */
rtlztnFcltSeComCd?: string;
/** 재이용용도구분공통코드 */
rtlztnUsgSeComCd?: string;
}
/** 설치인가관리 수정 요청 DTO */
export type InstlSncnMngUpdateRequestDto = InstlSncnMngSaveRequestDto;
/** 설치인가관리 응답 DTO */
export interface InstlSncnMngResponseDto extends InstlSncnMngSaveRequestDto {
/** 설치인가문서번호 (필수로 재정의) */
instlSncnDocNo: string;
/** 환경청구분공통코드명 */
envoSeCdNm: string;
/** 인가구분공통코드명 */
sncnSeCdNm: string;
/** 업무구분공통코드명 */
taskSeCdNm: string;
/** 진행상태공통코드명 */
prgrsSttsCdNm: string;
/** 재이용시설구분공통코드명 */
rtlztnFcltSeComNm: string;
/** 재이용용도구분공통코드명 */
rtlztnUsgSeComNm: string;
/** 등록일시 */
regDt: string;
mdfcnDt: string;
/** 등록자 UUID */
rgtr: string;
/** 등록자명 */
rgtrNm: string;
/** 수정 일시 */
mdfcnDt: string;
/** 시도 */
sido: string;
/** 구군 */
gugun: string;
}
/** 설치인가 신청 - 사업규모 저장 DTO(배열 원소) */
export interface InstlSncnBzScaleSaveDto {
export interface InstlSncnBzScaleSaveDto extends BzScaleSaveBase {
/** 공종구분코드 */
cnstpSeCd: string;
/** 기본계획(A) */
/** 기본계획수량 */
bscPl: number;
/** 예산반영(B) */
/** 예산반영수량 */
budgetReflct: number;
/** 금회설계시(C) */
/** 금회설계수량 */
thtiDsgn: number;
/** 증감(C-B) */
diff: number;
/** 증감(C-B) — 클라이언트 계산용, API 미전송 */
diff?: number;
}
/** 설치인가 신청 - 총사업비 저장 DTO(배열 원소) */
export interface InstlSncnBizCstSaveRequestDto {
export interface InstlSncnBizCstSaveRequestDto extends BizCstSaveBase {
/** 회계구분코드 */
acntgSeCd: string;
/** 기본계획금액 */
@@ -84,66 +155,90 @@ export interface InstlSncnBizCstSaveRequestDto {
}
/** 설치인가 신청 - 체크리스트 결과 저장 DTO(배열 원소) */
export interface InstlSncnCklstResultSaveDto {
export interface InstlSncnCklstResultSaveDto extends CklstResultSaveBase {
/** 점검표일련번호 */
cklstSn: number;
/** 점검표항목일련번호 */
cklstArtclSn: number;
/** 점검표검토일련번호 */
cklstRvwSn: number;
rvwStpComCd: string;
/** 선택값(Y/N/null) */
selected: "Y" | "N" | null;
/** 담당자검토내용(기술검토의견) */
picRvwCn?: string | null;
}
/** 설치인가 신청 - 필수첨부파일 저장 DTO(배열 원소) */
export interface InstlSncnEsntlAtchFileSaveDto {
export interface InstlSncnEsntlAtchFileSaveDto extends EsntlAtchFileSaveBase {
/** 필수첨부파일아이디 */
esntlAtchFileId: string;
/** 첨부파일그룹아이디 */
atchFileGroupId: string;
}
/** 설치인가검토의견 저장 요청 DTO */
export interface InstlSncnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase {
/** 설치인가문서번호 */
instlSncnDocNo: string;
}
/** 설치인가 보완요구 저장 DTO(배열 원소) */
export type InstlSncnSplmntDmndSaveDto = SplmntDmndSaveBase;
/** 설치인가 신청 - 통합 저장 요청 DTO */
export interface InstlSncnApplySaveRequestDto {
instlSncnMng: InstlSncnMng;
export interface InstlSncnApplySaveRequestDto extends PbadmsApplySaveHeader {
/** 설치인가관리 정보 */
instlSncnMng: InstlSncnMngSaveRequestDto;
/** 사업규모 목록 */
bzScaleList: InstlSncnBzScaleSaveDto[];
/** 총사업비 목록 */
bizCstList: InstlSncnBizCstSaveRequestDto[];
/** 체크리스트 검토결과 목록 */
cklstResultList: InstlSncnCklstResultSaveDto[];
/** 체크리스트 검토결과 맵 (rvwStpComCd -> 목록) */
cklstResultMap?: Record<string, InstlSncnCklstResultSaveDto[]>;
/** 필수첨부파일 목록 */
esntlAtchFileList: InstlSncnEsntlAtchFileSaveDto[];
/** 검토의견 목록 */
rvwOpnnList?: InstlSncnRvwOpnnSaveRequestDto[];
/** 보완요구 목록 */
splmntDmndList?: InstlSncnSplmntDmndSaveDto[];
}
export type ReviewStepCode = "LOCAL" | "ENV" | "KECO";
/** 설치인가검토의견 응답 DTO */
export type InstlSncnRvwOpnnResponseDto = InstlSncnRvwOpnnSaveRequestDto &
RvwOpnnResponseExtension & {
instlSncnDocNo: string;
rvwOpnnSn: number;
opnrSeComCd: string;
opnrNm: string;
rvwRsltCn: string;
rvwRsltAnmlyYn: string;
atchFileGroupId: string | null;
opnrSeCdNm: string | null;
regDt: string;
};
/** 전자결재 진행상태 (클라이언트 응답용) */
export interface ElaprDocPrgrsClientResponseDto {
elaprDocNo?: string;
elaprDmndId?: string;
elaprPrgrsId?: string;
atrzSeqNo?: number;
atrzSeComCd?: string;
atrzSeComNm?: string;
aprvrUuid?: string;
aprvrUuidNm?: string;
atrzPrgrsSttsComCd?: string;
atrzPrgrsSttsComNm?: string;
rjctRsn?: string | null;
atrzDt?: string | null;
jbpsSeNm?: string;
jbgdSeNm?: string;
ognzNm?: string;
}
// det 응답에 전자결재 진행상태 목록을 포함
export interface InstlSncnMngDetResponse {
detDto: InstlSncnMng;
bizSclList: any[];
bizCstList: any[];
cklstResultList?: any[];
esntlFileList: any[];
etcFileList: any[];
/** 설치인가관리 상세 응답 DTO */
export interface InstlSncnMngDetResponse
extends PbadmsMngDetResponseBase<InstlSncnMngResponseDto, InstlSncnRvwOpnnResponseDto> {
detDto: InstlSncnMngResponseDto;
bizSclList: BizSclTreeDto[];
bizCstList: BizCstTreeDto[];
esntlFileList: EsntlAtchFileMngTreeResponseDto[];
etcFileList: AtflMngClientResponseDto[];
elaprDocPrgrsList?: ElaprDocPrgrsClientResponseDto[] | null;
}
export interface InstlSncnMngService {
getPage: (params: InstlSncnMngSearchParams) => Promise<SpringPage<InstlSncnMng>>;
getPage: (params: InstlSncnMngSearchParams) => Promise<PageResponse<InstlSncnMngResponseDto>>;
getById: (id: string) => Promise<InstlSncnMngDetResponse>;
save: (payload: InstlSncnMngSaveRequestDto) => Promise<string>;
saveApply: (payload: InstlSncnApplySaveRequestDto) => Promise<string>;
update: (id: string, payload: InstlSncnMngUpdateRequestDto) => Promise<void>;
techReview: (id: string, payload: InstlSncnApplySaveRequestDto) => Promise<void>;
remove: (id: string) => Promise<void>;
changePrgrsSttsComCd: (payload: DocPrgrsUpdateRequestDto) => Promise<void>;
}
+17 -2
View File
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import type {
RvwMnpwMngPageSearchDto,
RvwMnpwMngSaveRequestDto,
RvwMnpwMngSearchDto,
RvwMnpwMngUpdateRequestDto,
@@ -10,9 +11,12 @@ import type {
import { rvwMnpwMngService } from "./rvw-mnpw-service";
const rvwMnpwMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.RVW_MNPW] as const;
const rvwMnpwMngBaseKey = [ENDPOINT.AS_SERVICE.RVW_MNPW] as const;
export function useRvwMnpwMngPage(params: RvwMnpwMngSearchDto, options?: { enabled?: boolean }) {
export function useRvwMnpwMngPage(
params: RvwMnpwMngPageSearchDto,
options?: { enabled?: boolean },
) {
return useQuery({
queryKey: [...rvwMnpwMngBaseKey, "findPage", params],
queryFn: () => rvwMnpwMngService.findPage(params),
@@ -20,6 +24,17 @@ export function useRvwMnpwMngPage(params: RvwMnpwMngSearchDto, options?: { enabl
});
}
export function useRvwMnpwMngLoginUserList(
searchDto: RvwMnpwMngSearchDto,
options?: { enabled?: boolean },
) {
return useQuery({
queryKey: [...rvwMnpwMngBaseKey, "findRvwLoginUserList", searchDto],
queryFn: () => rvwMnpwMngService.findRvwLoginUserList(searchDto),
enabled: options?.enabled,
});
}
export function useRvwMnpwMng(
atrzSeComCd: string | null | undefined,
ognzSn: number | null | undefined,
@@ -3,6 +3,7 @@ import { ENDPOINT } from "@/shared/config";
import type { PageResponse } from "@/shared/model";
import type {
RvwMnpwMngPageSearchDto,
RvwMnpwMngResponseDto,
RvwMnpwMngSaveRequestDto,
RvwMnpwMngSearchDto,
@@ -10,14 +11,21 @@ import type {
RvwMnpwMngUpdateRequestDto,
} from "../model/rvw-mnpw-interface";
const BASE = ENDPOINT.AS_COMMON_SERVICE.RVW_MNPW;
const BASE = ENDPOINT.AS_SERVICE.RVW_MNPW;
function pkPath(atrzSeComCd: string, ognzSn: number, userUuid: string): string {
return `${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`;
async function findPage(
params: RvwMnpwMngPageSearchDto,
): Promise<PageResponse<RvwMnpwMngResponseDto>> {
const response = await client.get<PageResponse<RvwMnpwMngResponseDto>>(BASE, { params });
return response.data;
}
async function findPage(params: RvwMnpwMngSearchDto): Promise<PageResponse<RvwMnpwMngResponseDto>> {
const response = await client.get<PageResponse<RvwMnpwMngResponseDto>>(BASE, { params });
async function findRvwLoginUserList(
searchDto: RvwMnpwMngSearchDto,
): Promise<PageResponse<RvwMnpwMngResponseDto>> {
const response = await client.get<PageResponse<RvwMnpwMngResponseDto>>(`${BASE}/loginuser/me`, {
params: searchDto,
});
return response.data;
}
@@ -26,41 +34,48 @@ async function findById(
ognzSn: number,
userUuid: string,
): Promise<RvwMnpwMngResponseDto> {
const response = await client.get<RvwMnpwMngResponseDto>(pkPath(atrzSeComCd, ognzSn, userUuid));
const response = await client.get<RvwMnpwMngResponseDto>(
`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`,
);
return response.data;
}
async function save(body: RvwMnpwMngSaveRequestDto): Promise<RvwMnpwMngResponseDto> {
const response = await client.post<RvwMnpwMngResponseDto>(BASE, body);
async function saveRvwMnpwMng(body: RvwMnpwMngSaveRequestDto): Promise<RvwMnpwMngResponseDto> {
const { atrzSeComCd, ognzSn, userUuid } = body;
const response = await client.post<RvwMnpwMngResponseDto>(
`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`,
body,
);
return response.data;
}
async function update(
async function updateRvwMnpwMng(
atrzSeComCd: string,
ognzSn: number,
userUuid: string,
body: RvwMnpwMngUpdateRequestDto,
): Promise<RvwMnpwMngResponseDto> {
const response = await client.put<RvwMnpwMngResponseDto>(
pkPath(atrzSeComCd, ognzSn, userUuid),
`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`,
body,
);
return response.data;
}
async function deleteRvwMnpw(
async function deleteRvwMnpwMng(
atrzSeComCd: string,
ognzSn: number,
userUuid: string,
): Promise<string> {
const response = await client.delete<string>(pkPath(atrzSeComCd, ognzSn, userUuid));
const response = await client.delete<string>(`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`);
return response.data;
}
export const rvwMnpwMngService = {
findPage,
findRvwLoginUserList,
findById,
save,
update,
delete: deleteRvwMnpw,
save: saveRvwMnpwMng,
update: updateRvwMnpwMng,
delete: deleteRvwMnpwMng,
} satisfies RvwMnpwMngService;
+2
View File
@@ -1,12 +1,14 @@
export {
useDeleteRvwMnpwMng,
useRvwMnpwMng,
useRvwMnpwMngLoginUserList,
useRvwMnpwMngPage,
useSaveRvwMnpwMng,
useUpdateRvwMnpwMng,
} from "./api/queries";
export { rvwMnpwMngService } from "./api/rvw-mnpw-service";
export type {
RvwMnpwMngPageSearchDto,
RvwMnpwMngResponseDto,
RvwMnpwMngSaveRequestDto,
RvwMnpwMngSearchDto,
@@ -14,13 +14,16 @@ export interface RvwMnpwMngResponseDto {
regDt?: string;
}
/** 백엔드 RvwMnpwMngSearchDto + Pageable */
export interface RvwMnpwMngSearchDto extends PageableParams {
/** 백엔드 RvwMnpwMngSearchDto */
export interface RvwMnpwMngSearchDto {
atrzSeComCd?: string;
ognzSn?: number;
userUuid?: string;
}
/** findPage — RvwMnpwMngSearchDto + Pageable */
export type RvwMnpwMngPageSearchDto = RvwMnpwMngSearchDto & PageableParams;
/** 백엔드 RvwMnpwMngSaveRequestDto */
export interface RvwMnpwMngSaveRequestDto {
atrzSeComCd: string;
@@ -35,7 +38,10 @@ export interface RvwMnpwMngUpdateRequestDto {
/** Controller public 메서드명·시그니처와 동일 */
export interface RvwMnpwMngService {
findPage(params: RvwMnpwMngSearchDto): Promise<PageResponse<RvwMnpwMngResponseDto>>;
findPage(params: RvwMnpwMngPageSearchDto): Promise<PageResponse<RvwMnpwMngResponseDto>>;
findRvwLoginUserList(
searchDto: RvwMnpwMngSearchDto,
): Promise<PageResponse<RvwMnpwMngResponseDto>>;
findById(atrzSeComCd: string, ognzSn: number, userUuid: string): Promise<RvwMnpwMngResponseDto>;
save(body: RvwMnpwMngSaveRequestDto): Promise<RvwMnpwMngResponseDto>;
update(
+1
View File
@@ -0,0 +1 @@
export { handleTusUpload, type TusUploadOptions } from "./lib/utils";
+61
View File
@@ -0,0 +1,61 @@
import { Upload } from "tus-js-client";
import { ENDPOINT, PROXY_HOST } from "@/shared/config";
import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config/env";
import { getCookie } from "@/shared/lib";
interface TusUploadOptions {
metadata?: Record<string, string>;
handleUploadStart?: (upload: Upload) => void;
handleProgress?: (percentage: number) => void;
handleSuccess?: (upload: Upload, atflCd: string, atflId: string) => void;
handleError?: (error: Error) => void;
}
function handleTusUpload(file: File, options?: TusUploadOptions) {
let atflCd = "";
let atflId = "";
const metadata = {
filename: file.name,
filetype: file.type,
...options?.metadata,
};
const upload = new Upload(file, {
endpoint: `${PROXY_HOST}${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`,
chunkSize: 10 * 1024 * 1024,
headers: {
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
[AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "",
},
metadata,
onError(error) {
console.error("TUS upload failed:", error);
options?.handleError?.(error);
},
onProgress(bytesUploaded, bytesTotal) {
const percentage = (bytesUploaded / bytesTotal) * 100;
options?.handleProgress?.(percentage);
},
onAfterResponse(_req, res) {
const fileInfo = res.getHeader("X-File-Info");
if (!fileInfo) return;
const parsed = JSON.parse(fileInfo) as { atflCd?: string; atflId?: string };
atflCd = parsed.atflCd ?? "";
atflId = parsed.atflId ?? "";
},
onSuccess() {
options?.handleSuccess?.(upload, atflCd, atflId);
},
});
upload.start();
options?.handleUploadStart?.(upload);
return upload;
}
export { handleTusUpload };
export type { TusUploadOptions };
@@ -1 +0,0 @@
export { AcntgCodeManagementPage } from "./ui/acntg-code-management-page";
@@ -1,245 +0,0 @@
import type { AcntgSeMngResponseDto } from "@/entities/acntg";
import type {
AcntgDisplaySlot,
AcntgSeBaseEntry,
AcntgSeGridState,
} from "../model/acntg-se-grid-types";
import type { AcntgSeRow } from "../model/acntg-se-row-types";
import { buildAcntgDisplayRows } from "../model/build-acntg-display-rows";
export function createLoadedRowKey(index: number, acntgSeCd: string): string {
return `loaded-${index}-${acntgSeCd}`;
}
export function createDraftRowKey(): string {
return `draft-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
export function createEmptyGridState(): AcntgSeGridState {
return {
baseByKey: {},
structure: [],
drafts: {},
patches: {},
deletedKeys: new Set(),
};
}
function toInitialRow(entry: AcntgSeBaseEntry): AcntgSeRow {
return {
...entry,
isNew: false,
isDirty: false,
isDeleted: false,
};
}
export function buildStructureFromBaseRows(
baseByKey: Record<string, AcntgSeBaseEntry>,
): AcntgDisplaySlot[] {
const rows = Object.values(baseByKey).map(toInitialRow);
const display = buildAcntgDisplayRows(rows);
const cdToRowKey = new Map(rows.map((row) => [row.acntgSeCd, row.rowKey]));
return display.map(({ row, depth }) => ({
rowKey: row.rowKey,
depth,
parentRowKey: row.upAcntgSeCd?.trim() ? (cdToRowKey.get(row.upAcntgSeCd.trim()) ?? null) : null,
}));
}
export function hydrateGridFromServer(content: AcntgSeMngResponseDto[]): AcntgSeGridState {
const baseByKey: Record<string, AcntgSeBaseEntry> = {};
content.forEach((item, index) => {
const rowKey = createLoadedRowKey(index, item.acntgSeCd);
baseByKey[rowKey] = { ...item, rowKey };
});
return {
baseByKey,
structure: buildStructureFromBaseRows(baseByKey),
drafts: {},
patches: {},
deletedKeys: new Set(),
};
}
export function resolveParentAcntgSeCd(
state: AcntgSeGridState,
parentRowKey: string | null,
): string | undefined {
if (!parentRowKey) return undefined;
const parent = mergeAcntgRow(state, parentRowKey);
const cd = parent?.acntgSeCd?.trim();
return cd || undefined;
}
export function mergeAcntgRow(state: AcntgSeGridState, rowKey: string): AcntgSeRow | null {
const draft = state.drafts[rowKey];
if (draft) {
return {
rowKey,
acntgSeCd: draft.acntgSeCd,
acntgSeCdNm: draft.acntgSeCdNm,
expln: draft.expln,
useYn: draft.useYn,
cdLvNo: draft.cdLvNo,
upAcntgSeCd: resolveParentAcntgSeCd(state, draft.parentRowKey),
isNew: true,
isDirty: true,
isDeleted: false,
};
}
const base = state.baseByKey[rowKey];
if (!base) return null;
const patch = state.patches[rowKey] ?? {};
const hasPatch = Object.keys(patch).length > 0;
return {
...base,
...patch,
rowKey,
isNew: false,
isDirty: hasPatch,
isDeleted: state.deletedKeys.has(rowKey),
};
}
export function mergeAllAcntgRows(state: AcntgSeGridState): AcntgSeRow[] {
const keys = new Set([...Object.keys(state.baseByKey), ...Object.keys(state.drafts)]);
return [...keys]
.map((rowKey) => mergeAcntgRow(state, rowKey))
.filter((row): row is AcntgSeRow => row != null);
}
export function hasAcntgGridPendingChanges(state: AcntgSeGridState): boolean {
return (
state.deletedKeys.size > 0 ||
Object.keys(state.drafts).length > 0 ||
Object.keys(state.patches).length > 0
);
}
export function collectDescendantRowKeys(
structure: AcntgDisplaySlot[],
rootRowKey: string,
): Set<string> {
const result = new Set<string>([rootRowKey]);
const queue = [rootRowKey];
while (queue.length > 0) {
const parentKey = queue.shift()!;
for (const slot of structure) {
if (slot.parentRowKey === parentKey && !result.has(slot.rowKey)) {
result.add(slot.rowKey);
queue.push(slot.rowKey);
}
}
}
return result;
}
export function insertRootSlot(
structure: AcntgDisplaySlot[],
slot: AcntgDisplaySlot,
): AcntgDisplaySlot[] {
const firstRootIndex = structure.findIndex((item) => item.parentRowKey === null);
if (firstRootIndex === -1) return [slot, ...structure];
return [...structure.slice(0, firstRootIndex), slot, ...structure.slice(firstRootIndex)];
}
export function insertChildSlot(
structure: AcntgDisplaySlot[],
parentRowKey: string,
slot: AcntgDisplaySlot,
): AcntgDisplaySlot[] {
const parentIndex = structure.findIndex((item) => item.rowKey === parentRowKey);
if (parentIndex === -1) return [...structure, slot];
const insertAt = parentIndex + 1;
return [...structure.slice(0, insertAt), slot, ...structure.slice(insertAt)];
}
export function removeSlotsFromStructure(
structure: AcntgDisplaySlot[],
rowKeysToRemove: Set<string>,
): AcntgDisplaySlot[] {
return structure.filter((slot) => !rowKeysToRemove.has(slot.rowKey));
}
export function patchAcntgGridRow(
state: AcntgSeGridState,
rowKey: string,
patch: Partial<AcntgSeRow>,
): AcntgSeGridState {
const draft = state.drafts[rowKey];
if (draft) {
return {
...state,
drafts: {
...state.drafts,
[rowKey]: {
...draft,
...(patch.acntgSeCd !== undefined ? { acntgSeCd: patch.acntgSeCd } : {}),
...(patch.acntgSeCdNm !== undefined ? { acntgSeCdNm: patch.acntgSeCdNm ?? "" } : {}),
...(patch.expln !== undefined ? { expln: patch.expln ?? "" } : {}),
...(patch.useYn !== undefined ? { useYn: patch.useYn } : {}),
},
},
};
}
const { rowKey: _rk, isNew: _n, isDirty: _d, isDeleted: _del, ...dtoPatch } = patch;
return {
...state,
patches: {
...state.patches,
[rowKey]: { ...state.patches[rowKey], ...dtoPatch },
},
};
}
export function markAcntgRowsDeleted(
state: AcntgSeGridState,
rootRowKey: string,
): AcntgSeGridState {
const keysToDelete = collectDescendantRowKeys(state.structure, rootRowKey);
const deletedKeys = new Set(state.deletedKeys);
for (const key of keysToDelete) {
deletedKeys.add(key);
}
return { ...state, deletedKeys };
}
export function validateDraftParentAcntgSeCd(
state: AcntgSeGridState,
rowKey: string,
): string | null {
const draft = state.drafts[rowKey];
if (!draft?.parentRowKey) return null;
const parent = mergeAcntgRow(state, draft.parentRowKey);
if (!parent?.acntgSeCd?.trim()) {
return "하위 항목을 추가하려면 상위 코드를 먼저 입력하세요.";
}
return null;
}
export function removeDraftFromGrid(state: AcntgSeGridState, rowKey: string): AcntgSeGridState {
const { [rowKey]: _removed, ...restDrafts } = state.drafts;
const keysToRemove = collectDescendantRowKeys(state.structure, rowKey);
return {
...state,
drafts: restDrafts,
structure: removeSlotsFromStructure(state.structure, keysToRemove),
};
}
@@ -1,62 +0,0 @@
import { ACNTG_SE_SAVE_MODE, type AcntgSeMngSaveRequestDto } from "@/entities/acntg";
import type { AcntgSeRow } from "../model/acntg-se-row-types";
import { getVisibleRows } from "./row-state";
function compareCdLvNoDesc(a: AcntgSeRow, b: AcntgSeRow): number {
return (b.cdLvNo ?? 1) - (a.cdLvNo ?? 1);
}
function compareCdLvNoAsc(a: AcntgSeRow, b: AcntgSeRow): number {
return (a.cdLvNo ?? 1) - (b.cdLvNo ?? 1);
}
export function buildSaveMultiPayload(rows: AcntgSeRow[]): AcntgSeMngSaveRequestDto[] {
const payload: AcntgSeMngSaveRequestDto[] = [];
const deletedRows = rows.filter((row) => row.isDeleted && !row.isNew).sort(compareCdLvNoDesc);
for (const row of deletedRows) {
payload.push({
mode: ACNTG_SE_SAVE_MODE.DELETE,
acntgSeCd: row.acntgSeCd,
acntgSeCdNm: row.acntgSeCdNm?.trim() || "",
upAcntgSeCd: row.upAcntgSeCd,
cdLvNo: row.cdLvNo,
useYn: row.useYn,
expln: row.expln?.trim() || undefined,
});
}
const visibleRows = getVisibleRows(rows).sort(compareCdLvNoAsc);
for (const row of visibleRows) {
if (row.isNew) {
payload.push({
mode: ACNTG_SE_SAVE_MODE.CREATE,
acntgSeCd: row.acntgSeCd.trim(),
acntgSeCdNm: row.acntgSeCdNm.trim(),
upAcntgSeCd: row.upAcntgSeCd,
cdLvNo: row.cdLvNo,
useYn: row.useYn,
expln: row.expln?.trim() || undefined,
});
continue;
}
if (row.isDirty) {
payload.push({
mode: ACNTG_SE_SAVE_MODE.UPDATE,
acntgSeCd: row.acntgSeCd.trim(),
acntgSeCdNm: row.acntgSeCdNm.trim(),
upAcntgSeCd: row.upAcntgSeCd,
cdLvNo: row.cdLvNo,
useYn: row.useYn,
expln: row.expln?.trim() || undefined,
});
}
}
return payload;
}
@@ -1,5 +0,0 @@
import type { AcntgSeRow } from "../model/acntg-se-row-types";
export function getVisibleRows(rows: AcntgSeRow[]): AcntgSeRow[] {
return rows.filter((row) => !row.isDeleted);
}
@@ -1,172 +0,0 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { AcntgSeMngResponseDto } from "@/entities/acntg";
import type { AcntgSeGridState } from "../model/acntg-se-grid-types";
import type { AcntgSeRow } from "../model/acntg-se-row-types";
import {
createDraftRowKey,
createEmptyGridState,
hasAcntgGridPendingChanges,
hydrateGridFromServer,
insertChildSlot,
insertRootSlot,
markAcntgRowsDeleted,
mergeAcntgRow,
mergeAllAcntgRows,
patchAcntgGridRow,
removeDraftFromGrid,
validateDraftParentAcntgSeCd,
} from "./acntg-se-grid";
import { buildSaveMultiPayload } from "./build-save-multi-payload";
function validateAcntgSeRow(row: AcntgSeRow): string | null {
if (!row.acntgSeCd.trim()) return "코드를 입력하세요.";
if (!row.acntgSeCdNm?.trim()) return "코드명을 입력하세요.";
const parentCd = row.upAcntgSeCd?.trim();
if (parentCd && row.acntgSeCd.trim() === parentCd) {
return "상위 코드와 동일한 코드는 사용할 수 없습니다.";
}
return null;
}
interface UseAcntgSeRowsOptions {
serverContent: AcntgSeMngResponseDto[] | undefined;
onAlert: (message: string) => void;
onSave: (payload: ReturnType<typeof buildSaveMultiPayload>) => Promise<void>;
}
export function useAcntgSeRows({ serverContent, onAlert, onSave }: UseAcntgSeRowsOptions) {
const [grid, setGrid] = useState<AcntgSeGridState>(createEmptyGridState);
const hasPendingRef = useRef(false);
hasPendingRef.current = hasAcntgGridPendingChanges(grid);
useEffect(() => {
if (serverContent == null) return;
if (hasPendingRef.current) return;
setGrid(hydrateGridFromServer(serverContent));
}, [serverContent]);
const displayRows = useMemo(() => {
return grid.structure
.filter((slot) => !grid.deletedKeys.has(slot.rowKey))
.map((slot) => {
const row = mergeAcntgRow(grid, slot.rowKey);
return row ? { row, depth: slot.depth } : null;
})
.filter((item): item is { row: AcntgSeRow; depth: number } => item != null);
}, [grid]);
const canSave = hasAcntgGridPendingChanges(grid);
const updateRow = useCallback((rowKey: string, patch: Partial<AcntgSeRow>) => {
setGrid((prev) => patchAcntgGridRow(prev, rowKey, patch));
}, []);
const addRootRow = useCallback(() => {
const rowKey = createDraftRowKey();
setGrid((prev) => ({
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: null,
cdLvNo: 1,
acntgSeCd: "",
acntgSeCdNm: "",
expln: "",
useYn: "Y",
},
},
structure: insertRootSlot(prev.structure, { rowKey, depth: 0, parentRowKey: null }),
}));
}, []);
const addChildRow = useCallback((parent: AcntgSeRow) => {
const rowKey = createDraftRowKey();
const slot = {
rowKey,
depth: 0,
parentRowKey: parent.rowKey,
};
setGrid((prev) => {
const parentSlot = prev.structure.find((item) => item.rowKey === parent.rowKey);
const depth = (parentSlot?.depth ?? 0) + 1;
return {
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: parent.rowKey,
cdLvNo: (parent.cdLvNo ?? 1) + 1,
acntgSeCd: "",
acntgSeCdNm: "",
expln: "",
useYn: "Y",
},
},
structure: insertChildSlot(prev.structure, parent.rowKey, {
...slot,
depth,
}),
};
});
}, []);
const removeDraft = useCallback((rowKey: string) => {
setGrid((prev) => removeDraftFromGrid(prev, rowKey));
}, []);
const markDeleted = useCallback((rowKey: string) => {
setGrid((prev) => markAcntgRowsDeleted(prev, rowKey));
}, []);
const resetFromServer = useCallback((content: AcntgSeMngResponseDto[]) => {
setGrid(hydrateGridFromServer(content));
}, []);
const save = useCallback(async () => {
const mergedRows = mergeAllAcntgRows(grid);
const payload = buildSaveMultiPayload(mergedRows);
if (payload.length === 0) return false;
for (const row of mergedRows) {
if (!row.isDeleted && (row.isNew || row.isDirty)) {
const parentCodeError = validateDraftParentAcntgSeCd(grid, row.rowKey);
if (parentCodeError) {
onAlert(parentCodeError);
return false;
}
const fieldError = validateAcntgSeRow(row);
if (fieldError) {
onAlert(fieldError);
return false;
}
}
}
await onSave(payload);
return true;
}, [grid, onAlert, onSave]);
return {
displayRows,
canSave,
updateRow,
addRootRow,
addChildRow,
removeDraft,
markDeleted,
resetFromServer,
save,
};
}
@@ -1,148 +0,0 @@
"use client";
import { useCallback } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore } from "@/shared/lib";
import { useAcntgSeMngPage, useSaveMultiAcntgSeMng } from "@/entities/acntg";
import {
Button,
Skeleton,
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "@common/components";
import { useAcntgSeRows } from "../lib/use-acntg-se-rows";
import type { AcntgSeRow } from "../model/acntg-se-row-types";
import { AcntgSeRowComponent } from "./acntg-se-row";
function AcntgCodeManagementPage() {
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const pageQuery = useAcntgSeMngPage({
page: 0,
size: 500,
sort: "cdLvNo,asc",
});
const saveMultiMutation = useSaveMultiAcntgSeMng();
const handlePersist = useCallback(
async (payload: Parameters<typeof saveMultiMutation.mutateAsync>[0]) => {
await saveMultiMutation.mutateAsync(payload);
},
[saveMultiMutation],
);
const {
displayRows,
canSave,
updateRow,
addRootRow,
addChildRow,
removeDraft,
markDeleted,
resetFromServer,
save,
} = useAcntgSeRows({
serverContent: pageQuery.data?.content,
onAlert: alert,
onSave: handlePersist,
});
const handleDelete = async (row: AcntgSeRow) => {
if (!(await confirm("삭제하시겠습니까?"))) return;
if (row.isNew) {
removeDraft(row.rowKey);
return;
}
markDeleted(row.rowKey);
};
const handleSave = async () => {
if (!(await confirm("저장하시겠습니까?"))) return;
const saved = await save();
if (!saved) return;
alert("저장되었습니다.");
const result = await pageQuery.refetch();
resetFromServer(result.data?.content ?? []);
};
const isSaving = saveMultiMutation.isPending;
return (
<div className="space-y-6 px-[1.8rem] pb-6">
<section className="card card-border mx-0 bg-background pb-6 pt-0">
<div className="card-header flex flex-wrap items-center justify-between gap-3">
<h3 className="text-2xl font-semibold">회계코드 관리</h3>
<div className="flex flex-wrap items-center gap-3">
<Button type="button" color="green" onClick={addRootRow}>
코드 추가
</Button>
<Button type="button" disabled={!canSave || isSaving} onClick={handleSave}>
저장
</Button>
</div>
</div>
{pageQuery.isPending ? (
<div className="flex flex-col gap-2 px-4 pb-4">
<Skeleton className="h-[4rem]" />
<Skeleton className="h-[16rem]" />
</div>
) : displayRows.length === 0 ? (
<p className="py-8 text-center text-lg text-muted-foreground">
등록된 코드가 없습니다. [코드 추가]로 1단계 코드를 등록하세요.
</p>
) : (
<div className="max-h-[60rem] overflow-y-auto pt-3">
<Table className="w-full">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="35%" />
<col width="12%" />
<col width="8%" />
<col width="10%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>코드</TableHead>
<TableHead>코드명</TableHead>
<TableHead>설명</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => (
<AcntgSeRowComponent
key={row.rowKey}
row={row}
depth={depth}
onChange={(patch) => updateRow(row.rowKey, patch)}
onAddChild={() => addChildRow(row)}
onDelete={() => handleDelete(row)}
/>
))}
</TableBody>
</Table>
</div>
)}
</section>
</div>
);
}
export { AcntgCodeManagementPage };
@@ -1,6 +0,0 @@
import type { ElaprAplnRow } from "../model/apln-row-types";
/** 그리드 행 순서(1-based)로 결재순서번호 일괄 채번 */
export function assignAtrzSeqNumbers(rows: ElaprAplnRow[]): ElaprAplnRow[] {
return rows.map((row, index) => ({ ...row, atrzSeqNo: index + 1 }));
}
@@ -1,32 +0,0 @@
import type { ElaprApln } from "@/entities/elapr";
import { assignAtrzSeqNumbers } from "./assign-atrz-seq";
import { getVisibleRows } from "./validators";
import type { ElaprAplnRow } from "../model/apln-row-types";
function rowDiffersFromServer(row: ElaprAplnRow, serverRow: ElaprApln): boolean {
return (
row.aprvrUuid !== serverRow.aprvrUuid ||
row.atrzSeComCd !== serverRow.atrzSeComCd ||
row.atrzSeqNo !== serverRow.atrzSeqNo
);
}
export function hasUnsavedChanges(rows: ElaprAplnRow[], serverRows: ElaprApln[]): boolean {
if (rows.some((row) => row.isDeleted && row.aplnId)) return true;
if (rows.some((row) => row.isNew && !row.isDeleted)) return true;
const visibleWithSeq = assignAtrzSeqNumbers(getVisibleRows(rows));
for (const row of visibleWithSeq) {
if (row.isNew) continue;
if (!row.aplnId) continue;
if (row.isDirty) return true;
const serverRow = serverRows.find((s) => s.aplnId === row.aplnId);
if (!serverRow) continue;
if (rowDiffersFromServer(row, serverRow)) return true;
}
return false;
}
@@ -7,7 +7,19 @@ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore } from "@/shared/lib";
import { useElaprAplnPage, useSaveMultiElaprApln } from "@/entities/elapr";
import {
buildSaveMultiPayload,
type ElaprAplnRow,
hasUnsavedChanges,
useAplnRows,
useDndSensors,
useElaprAplnPage,
useSaveMultiElaprApln,
validateAllRowsForSave,
validateAplnRow,
validateAtrzOrder,
validateUniqueSeqNo,
} from "@/entities/elapr";
import {
Button,
Skeleton,
@@ -18,17 +30,6 @@ import {
TableRow,
} from "@common/components";
import { buildSaveMultiPayload } from "../lib/build-save-multi-payload";
import { hasUnsavedChanges } from "../lib/has-unsaved-changes";
import { useAplnRows } from "../lib/use-apln-rows";
import { useDndSensors } from "../lib/use-dnd-sensors";
import {
validateAllRowsForSave,
validateAplnRow,
validateAtrzOrder,
validateUniqueSeqNo,
} from "../lib/validators";
import type { ElaprAplnRow } from "../model/apln-row-types";
import type { ReviewerOption } from "../model/reviewer-option";
import { AprvrPickerDialog } from "./aprvr-picker-dialog";
@@ -92,7 +92,7 @@ function ElaprTmpltFormDialog({
<div className="space-y-2">
<Label>문서구분</Label>
<CodeSelect
upCd="ASDOCSECD"
upCd="ASDOCSE"
value={docSeComCd}
onValueChange={(v) => setValue("docSeComCd", v)}
placeholder="문서구분 선택"
@@ -5,10 +5,9 @@ import { CSS } from "@dnd-kit/utilities";
import { Equal } from "lucide-react";
import { CodeSelect, findCodeLabel, useCodeList } from "@/entities/code";
import type { ElaprAplnRow } from "@/entities/elapr";
import { Button, TableCell, TableRow } from "@common/components";
import type { ElaprAplnRow } from "../model/apln-row-types";
interface SortableAplnRowProps {
row: ElaprAplnRow;
displaySeqNo: number;
@@ -102,7 +102,7 @@ function CklstMngFormDialog({
<div className="space-y-2">
<Label>문서구분</Label>
<CodeSelect
upCd="ASDOCSECD"
upCd="ASDOCSE"
value={docSeComCd}
onValueChange={(v) => setValue("docSeComCd", v)}
placeholder="문서구분 선택"
@@ -112,7 +112,7 @@ function CklstMngFormDialog({
<div className="space-y-2">
<Label>업무구분</Label>
<CodeSelect
upCd={docSeComCd || "ASDOCSECD"}
upCd={docSeComCd || "ASDOCSE"}
value={watch("taskSeComCd")}
onValueChange={(v) => setValue("taskSeComCd", v)}
placeholder="업무구분 선택"
@@ -67,6 +67,7 @@ function CklstPreviewDialog({
<TableBody>
{displayRows.map(({ row, depth }) => {
const branchPrefix = formatTreeBranchPrefix(depth);
const showRadios = row.bchckUseYn !== "N";
if (row.useYn === "N") return null;
@@ -82,16 +83,20 @@ function CklstPreviewDialog({
</TableCell>
<TableCell>{row.rvwCn ?? "—"}</TableCell>
<TableCell align="center">
<RadioGroup className="flex items-center justify-center">
<div className="flex items-center gap-1">
<RadioGroupItem value="Y" id={`${row.rowKey}-use-y`} />
<Label htmlFor={`${row.rowKey}-use-y`}>예</Label>
</div>
<div className="flex items-center gap-1">
<RadioGroupItem value="N" id={`${row.rowKey}-use-n`} />
<Label htmlFor={`${row.rowKey}-use-n`}>아니오</Label>
</div>
</RadioGroup>
{showRadios ? (
<RadioGroup className="flex items-center justify-center">
<div className="flex items-center gap-1">
<RadioGroupItem value="Y" id={`${row.rowKey}-preview-y`} />
<Label htmlFor={`${row.rowKey}-preview-y`}>예</Label>
</div>
<div className="flex items-center gap-1">
<RadioGroupItem value="N" id={`${row.rowKey}-preview-n`} />
<Label htmlFor={`${row.rowKey}-preview-n`}>아니오</Label>
</div>
</RadioGroup>
) : (
"—"
)}
</TableCell>
</TableRow>
);
@@ -113,6 +113,7 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
rvwCnLvNo: 1,
rvwCn: "",
useYn: "Y",
bchckUseYn: "Y",
sortSeq: maxSort + 1,
isNew: true,
isDirty: true,
@@ -145,6 +146,7 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
rvwCnLvNo: parentLv + 1,
rvwCn: "",
useYn: "Y",
bchckUseYn: "Y",
sortSeq: maxSort + 1,
isNew: true,
isDirty: true,
@@ -189,6 +191,8 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
const handleSave = async () => {
if (!cklstSn) return;
if (!(await confirm("체크리스트 항목을 저장하시겠습니까?"))) return;
const dirty = getDirtyRows().sort((a, b) => (a.rvwCnLvNo ?? 1) - (b.rvwCnLvNo ?? 1));
for (const row of dirty) {
@@ -198,8 +202,6 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
return;
}
if (!(await confirm("체크리스트 항목을 저장하시겠습니까?"))) return;
if (row.isNew) {
await createMutation.mutateAsync({
cklstSn: row.cklstSn,
@@ -209,7 +211,7 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
rvwCn: row.rvwCn,
useYn: row.useYn,
sortSeq: row.sortSeq,
bchckUseYn: "Y",
bchckUseYn: row.bchckUseYn ?? "Y",
});
} else if (row.cklstRvwSn != null) {
await updateMutation.mutateAsync({
@@ -277,17 +279,19 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
) : (
<Table className="w-full">
<colgroup>
<col width="20%" />
<col width="50%" />
<col width="15%" />
<col width="18%" />
<col width="42%" />
<col width="12%" />
<col width="12%" />
<col width="5%" />
<col width="10%" />
<col width="11%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>점검항목</TableHead>
<TableHead>주요 검토내용</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>체크 사용</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
@@ -91,6 +91,22 @@ function CklstRvwRowComponent({
</div>
</RadioGroup>
</TableCell>
<TableCell align="center">
<RadioGroup
value={row.bchckUseYn ?? "Y"}
onValueChange={(v) => onChange({ bchckUseYn: v as "Y" | "N", isDirty: true })}
className="flex justify-center gap-4"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="Y" id={`${row.rowKey}-bchck-y`} />
<Label htmlFor={`${row.rowKey}-bchck-y`}>예</Label>
</div>
<div className="flex items-center gap-2">
<RadioGroupItem value="N" id={`${row.rowKey}-bchck-n`} />
<Label htmlFor={`${row.rowKey}-bchck-n`}>아니오</Label>
</div>
</RadioGroup>
</TableCell>
<TableCell align="center">{levelLabel}</TableCell>
<TableCell align="center">
<div className="flex justify-center gap-2">
@@ -1 +0,0 @@
export { CnstpCodeManagementPage } from "./ui/cnstp-code-management-page";
@@ -1,62 +0,0 @@
import { CNSTP_SE_SAVE_MODE, type CnstpSeMngSaveRequestDto } from "@/entities/cnstp";
import type { CnstpSeRow } from "../model/cnstp-se-row-types";
import { getVisibleRows } from "./row-state";
function compareCdLvNoDesc(a: CnstpSeRow, b: CnstpSeRow): number {
return (b.cdLvNo ?? 1) - (a.cdLvNo ?? 1);
}
function compareCdLvNoAsc(a: CnstpSeRow, b: CnstpSeRow): number {
return (a.cdLvNo ?? 1) - (b.cdLvNo ?? 1);
}
export function buildSaveMultiPayload(rows: CnstpSeRow[]): CnstpSeMngSaveRequestDto[] {
const payload: CnstpSeMngSaveRequestDto[] = [];
const deletedRows = rows.filter((row) => row.isDeleted && !row.isNew).sort(compareCdLvNoDesc);
for (const row of deletedRows) {
payload.push({
mode: CNSTP_SE_SAVE_MODE.DELETE,
cnstpSeCd: row.cnstpSeCd,
cnstpSeCdNm: row.cnstpSeCdNm?.trim() || undefined,
upCnstpCd: row.upCnstpCd,
cdLvNo: row.cdLvNo,
useYn: row.useYn,
expln: row.expln?.trim() || undefined,
});
}
const visibleRows = getVisibleRows(rows).sort(compareCdLvNoAsc);
for (const row of visibleRows) {
if (row.isNew) {
payload.push({
mode: CNSTP_SE_SAVE_MODE.CREATE,
cnstpSeCd: row.cnstpSeCd.trim(),
cnstpSeCdNm: row.cnstpSeCdNm?.trim() || undefined,
upCnstpCd: row.upCnstpCd,
cdLvNo: row.cdLvNo,
useYn: row.useYn,
expln: row.expln?.trim() || undefined,
});
continue;
}
if (row.isDirty) {
payload.push({
mode: CNSTP_SE_SAVE_MODE.UPDATE,
cnstpSeCd: row.cnstpSeCd.trim(),
cnstpSeCdNm: row.cnstpSeCdNm?.trim() || undefined,
upCnstpCd: row.upCnstpCd,
cdLvNo: row.cdLvNo,
useYn: row.useYn,
expln: row.expln?.trim() || undefined,
});
}
}
return payload;
}
@@ -1,245 +0,0 @@
import type { CnstpSeMngResponseDto } from "@/entities/cnstp";
import { buildCnstpDisplayRows } from "../model/build-cnstp-display-rows";
import type {
CnstpDisplaySlot,
CnstpSeBaseEntry,
CnstpSeGridState,
} from "../model/cnstp-se-grid-types";
import type { CnstpSeRow } from "../model/cnstp-se-row-types";
export function createLoadedRowKey(index: number, cnstpSeCd: string): string {
return `loaded-${index}-${cnstpSeCd}`;
}
export function createDraftRowKey(): string {
return `draft-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
}
export function createEmptyGridState(): CnstpSeGridState {
return {
baseByKey: {},
structure: [],
drafts: {},
patches: {},
deletedKeys: new Set(),
};
}
function toInitialRow(entry: CnstpSeBaseEntry): CnstpSeRow {
return {
...entry,
isNew: false,
isDirty: false,
isDeleted: false,
};
}
export function buildStructureFromBaseRows(
baseByKey: Record<string, CnstpSeBaseEntry>,
): CnstpDisplaySlot[] {
const rows = Object.values(baseByKey).map(toInitialRow);
const display = buildCnstpDisplayRows(rows);
const cdToRowKey = new Map(rows.map((row) => [row.cnstpSeCd, row.rowKey]));
return display.map(({ row, depth }) => ({
rowKey: row.rowKey,
depth,
parentRowKey: row.upCnstpCd?.trim() ? (cdToRowKey.get(row.upCnstpCd.trim()) ?? null) : null,
}));
}
export function hydrateGridFromServer(content: CnstpSeMngResponseDto[]): CnstpSeGridState {
const baseByKey: Record<string, CnstpSeBaseEntry> = {};
content.forEach((item, index) => {
const rowKey = createLoadedRowKey(index, item.cnstpSeCd);
baseByKey[rowKey] = { ...item, rowKey };
});
return {
baseByKey,
structure: buildStructureFromBaseRows(baseByKey),
drafts: {},
patches: {},
deletedKeys: new Set(),
};
}
export function resolveParentCnstpSeCd(
state: CnstpSeGridState,
parentRowKey: string | null,
): string | undefined {
if (!parentRowKey) return undefined;
const parent = mergeCnstpRow(state, parentRowKey);
const cd = parent?.cnstpSeCd?.trim();
return cd || undefined;
}
export function mergeCnstpRow(state: CnstpSeGridState, rowKey: string): CnstpSeRow | null {
const draft = state.drafts[rowKey];
if (draft) {
return {
rowKey,
cnstpSeCd: draft.cnstpSeCd,
cnstpSeCdNm: draft.cnstpSeCdNm,
expln: draft.expln,
useYn: draft.useYn,
cdLvNo: draft.cdLvNo,
upCnstpCd: resolveParentCnstpSeCd(state, draft.parentRowKey),
isNew: true,
isDirty: true,
isDeleted: false,
};
}
const base = state.baseByKey[rowKey];
if (!base) return null;
const patch = state.patches[rowKey] ?? {};
const hasPatch = Object.keys(patch).length > 0;
return {
...base,
...patch,
rowKey,
isNew: false,
isDirty: hasPatch,
isDeleted: state.deletedKeys.has(rowKey),
};
}
export function mergeAllCnstpRows(state: CnstpSeGridState): CnstpSeRow[] {
const keys = new Set([...Object.keys(state.baseByKey), ...Object.keys(state.drafts)]);
return [...keys]
.map((rowKey) => mergeCnstpRow(state, rowKey))
.filter((row): row is CnstpSeRow => row != null);
}
export function hasCnstpGridPendingChanges(state: CnstpSeGridState): boolean {
return (
state.deletedKeys.size > 0 ||
Object.keys(state.drafts).length > 0 ||
Object.keys(state.patches).length > 0
);
}
export function collectDescendantRowKeys(
structure: CnstpDisplaySlot[],
rootRowKey: string,
): Set<string> {
const result = new Set<string>([rootRowKey]);
const queue = [rootRowKey];
while (queue.length > 0) {
const parentKey = queue.shift()!;
for (const slot of structure) {
if (slot.parentRowKey === parentKey && !result.has(slot.rowKey)) {
result.add(slot.rowKey);
queue.push(slot.rowKey);
}
}
}
return result;
}
export function insertRootSlot(
structure: CnstpDisplaySlot[],
slot: CnstpDisplaySlot,
): CnstpDisplaySlot[] {
const firstRootIndex = structure.findIndex((item) => item.parentRowKey === null);
if (firstRootIndex === -1) return [slot, ...structure];
return [...structure.slice(0, firstRootIndex), slot, ...structure.slice(firstRootIndex)];
}
export function insertChildSlot(
structure: CnstpDisplaySlot[],
parentRowKey: string,
slot: CnstpDisplaySlot,
): CnstpDisplaySlot[] {
const parentIndex = structure.findIndex((item) => item.rowKey === parentRowKey);
if (parentIndex === -1) return [...structure, slot];
const insertAt = parentIndex + 1;
return [...structure.slice(0, insertAt), slot, ...structure.slice(insertAt)];
}
export function removeSlotsFromStructure(
structure: CnstpDisplaySlot[],
rowKeysToRemove: Set<string>,
): CnstpDisplaySlot[] {
return structure.filter((slot) => !rowKeysToRemove.has(slot.rowKey));
}
export function patchCnstpGridRow(
state: CnstpSeGridState,
rowKey: string,
patch: Partial<CnstpSeRow>,
): CnstpSeGridState {
const draft = state.drafts[rowKey];
if (draft) {
return {
...state,
drafts: {
...state.drafts,
[rowKey]: {
...draft,
...(patch.cnstpSeCd !== undefined ? { cnstpSeCd: patch.cnstpSeCd } : {}),
...(patch.cnstpSeCdNm !== undefined ? { cnstpSeCdNm: patch.cnstpSeCdNm ?? "" } : {}),
...(patch.expln !== undefined ? { expln: patch.expln ?? "" } : {}),
...(patch.useYn !== undefined ? { useYn: patch.useYn } : {}),
},
},
};
}
const { rowKey: _rk, isNew: _n, isDirty: _d, isDeleted: _del, ...dtoPatch } = patch;
return {
...state,
patches: {
...state.patches,
[rowKey]: { ...state.patches[rowKey], ...dtoPatch },
},
};
}
export function markCnstpRowsDeleted(
state: CnstpSeGridState,
rootRowKey: string,
): CnstpSeGridState {
const keysToDelete = collectDescendantRowKeys(state.structure, rootRowKey);
const deletedKeys = new Set(state.deletedKeys);
for (const key of keysToDelete) {
deletedKeys.add(key);
}
return { ...state, deletedKeys };
}
export function validateDraftParentCnstpSeCd(
state: CnstpSeGridState,
rowKey: string,
): string | null {
const draft = state.drafts[rowKey];
if (!draft?.parentRowKey) return null;
const parent = mergeCnstpRow(state, draft.parentRowKey);
if (!parent?.cnstpSeCd?.trim()) {
return "하위 항목을 추가하려면 상위 코드를 먼저 입력하세요.";
}
return null;
}
export function removeDraftFromGrid(state: CnstpSeGridState, rowKey: string): CnstpSeGridState {
const { [rowKey]: _removed, ...restDrafts } = state.drafts;
const keysToRemove = collectDescendantRowKeys(state.structure, rowKey);
return {
...state,
drafts: restDrafts,
structure: removeSlotsFromStructure(state.structure, keysToRemove),
};
}
@@ -1,5 +0,0 @@
import type { CnstpSeRow } from "../model/cnstp-se-row-types";
export function getVisibleRows(rows: CnstpSeRow[]): CnstpSeRow[] {
return rows.filter((row) => !row.isDeleted);
}
@@ -1,167 +0,0 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { CnstpSeMngResponseDto } from "@/entities/cnstp";
import type { CnstpSeGridState } from "../model/cnstp-se-grid-types";
import type { CnstpSeRow } from "../model/cnstp-se-row-types";
import { buildSaveMultiPayload } from "./build-save-multi-payload";
import {
createDraftRowKey,
createEmptyGridState,
hasCnstpGridPendingChanges,
hydrateGridFromServer,
insertChildSlot,
insertRootSlot,
markCnstpRowsDeleted,
mergeAllCnstpRows,
mergeCnstpRow,
patchCnstpGridRow,
removeDraftFromGrid,
validateDraftParentCnstpSeCd,
} from "./cnstp-se-grid";
function validateCnstpSeRow(row: CnstpSeRow): string | null {
if (!row.cnstpSeCd.trim()) return "코드를 입력하세요.";
const parentCd = row.upCnstpCd?.trim();
if (parentCd && row.cnstpSeCd.trim() === parentCd) {
return "상위 코드와 동일한 코드는 사용할 수 없습니다.";
}
return null;
}
interface UseCnstpSeRowsOptions {
serverContent: CnstpSeMngResponseDto[] | undefined;
onAlert: (message: string) => void;
onSave: (payload: ReturnType<typeof buildSaveMultiPayload>) => Promise<void>;
}
export function useCnstpSeRows({ serverContent, onAlert, onSave }: UseCnstpSeRowsOptions) {
const [grid, setGrid] = useState<CnstpSeGridState>(createEmptyGridState);
const hasPendingRef = useRef(false);
hasPendingRef.current = hasCnstpGridPendingChanges(grid);
useEffect(() => {
if (serverContent == null) return;
if (hasPendingRef.current) return;
setGrid(hydrateGridFromServer(serverContent));
}, [serverContent]);
const displayRows = useMemo(() => {
return grid.structure
.filter((slot) => !grid.deletedKeys.has(slot.rowKey))
.map((slot) => {
const row = mergeCnstpRow(grid, slot.rowKey);
return row ? { row, depth: slot.depth } : null;
})
.filter((item): item is { row: CnstpSeRow; depth: number } => item != null);
}, [grid]);
const canSave = hasCnstpGridPendingChanges(grid);
const updateRow = useCallback((rowKey: string, patch: Partial<CnstpSeRow>) => {
setGrid((prev) => patchCnstpGridRow(prev, rowKey, patch));
}, []);
const addRootRow = useCallback(() => {
const rowKey = createDraftRowKey();
setGrid((prev) => ({
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: null,
cdLvNo: 1,
cnstpSeCd: "",
cnstpSeCdNm: "",
expln: "",
useYn: "Y",
},
},
structure: insertRootSlot(prev.structure, { rowKey, depth: 0, parentRowKey: null }),
}));
}, []);
const addChildRow = useCallback((parent: CnstpSeRow) => {
const rowKey = createDraftRowKey();
setGrid((prev) => {
const parentSlot = prev.structure.find((item) => item.rowKey === parent.rowKey);
const depth = (parentSlot?.depth ?? 0) + 1;
return {
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: parent.rowKey,
cdLvNo: (parent.cdLvNo ?? 1) + 1,
cnstpSeCd: "",
cnstpSeCdNm: "",
expln: "",
useYn: "Y",
},
},
structure: insertChildSlot(prev.structure, parent.rowKey, {
rowKey,
depth,
parentRowKey: parent.rowKey,
}),
};
});
}, []);
const removeDraft = useCallback((rowKey: string) => {
setGrid((prev) => removeDraftFromGrid(prev, rowKey));
}, []);
const markDeleted = useCallback((rowKey: string) => {
setGrid((prev) => markCnstpRowsDeleted(prev, rowKey));
}, []);
const resetFromServer = useCallback((content: CnstpSeMngResponseDto[]) => {
setGrid(hydrateGridFromServer(content));
}, []);
const save = useCallback(async () => {
const mergedRows = mergeAllCnstpRows(grid);
const payload = buildSaveMultiPayload(mergedRows);
if (payload.length === 0) return false;
for (const row of mergedRows) {
if (!row.isDeleted && (row.isNew || row.isDirty)) {
const parentCodeError = validateDraftParentCnstpSeCd(grid, row.rowKey);
if (parentCodeError) {
onAlert(parentCodeError);
return false;
}
const fieldError = validateCnstpSeRow(row);
if (fieldError) {
onAlert(fieldError);
return false;
}
}
}
await onSave(payload);
return true;
}, [grid, onAlert, onSave]);
return {
displayRows,
canSave,
updateRow,
addRootRow,
addChildRow,
removeDraft,
markDeleted,
resetFromServer,
save,
};
}
@@ -1,148 +0,0 @@
"use client";
import { useCallback } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore } from "@/shared/lib";
import { useCnstpSeMngPage, useSaveMultiCnstpSeMng } from "@/entities/cnstp";
import {
Button,
Skeleton,
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "@common/components";
import { useCnstpSeRows } from "../lib/use-cnstp-se-rows";
import type { CnstpSeRow } from "../model/cnstp-se-row-types";
import { CnstpSeRowComponent } from "./cnstp-se-row";
function CnstpCodeManagementPage() {
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
const pageQuery = useCnstpSeMngPage({
page: 0,
size: 500,
sort: "cdLvNo,asc",
});
const saveMultiMutation = useSaveMultiCnstpSeMng();
const handlePersist = useCallback(
async (payload: Parameters<typeof saveMultiMutation.mutateAsync>[0]) => {
await saveMultiMutation.mutateAsync(payload);
},
[saveMultiMutation],
);
const {
displayRows,
canSave,
updateRow,
addRootRow,
addChildRow,
removeDraft,
markDeleted,
resetFromServer,
save,
} = useCnstpSeRows({
serverContent: pageQuery.data?.content,
onAlert: alert,
onSave: handlePersist,
});
const handleDelete = async (row: CnstpSeRow) => {
if (!(await confirm("삭제하시겠습니까?"))) return;
if (row.isNew) {
removeDraft(row.rowKey);
return;
}
markDeleted(row.rowKey);
};
const handleSave = async () => {
if (!(await confirm("저장하시겠습니까?"))) return;
const saved = await save();
if (!saved) return;
alert("저장되었습니다.");
const result = await pageQuery.refetch();
resetFromServer(result.data?.content ?? []);
};
const isSaving = saveMultiMutation.isPending;
return (
<div className="space-y-6 px-[1.8rem] pb-6">
<section className="card card-border mx-0 bg-background pb-6 pt-0">
<div className="card-header flex flex-wrap items-center justify-between gap-3">
<h3 className="text-2xl font-semibold">공종코드 관리</h3>
<div className="flex flex-wrap items-center gap-3">
<Button type="button" color="green" onClick={addRootRow}>
코드 추가
</Button>
<Button type="button" disabled={!canSave || isSaving} onClick={handleSave}>
저장
</Button>
</div>
</div>
{pageQuery.isPending ? (
<div className="flex flex-col gap-2 px-4 pb-4">
<Skeleton className="h-[4rem]" />
<Skeleton className="h-[16rem]" />
</div>
) : displayRows.length === 0 ? (
<p className="py-8 text-center text-lg text-muted-foreground">
등록된 코드가 없습니다. [코드 추가]로 1단계 코드를 등록하세요.
</p>
) : (
<div className="max-h-[60rem] overflow-y-auto pt-3">
<Table className="w-full">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="35%" />
<col width="12%" />
<col width="8%" />
<col width="10%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>코드</TableHead>
<TableHead>코드명</TableHead>
<TableHead>설명</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => (
<CnstpSeRowComponent
key={row.rowKey}
row={row}
depth={depth}
onChange={(patch) => updateRow(row.rowKey, patch)}
onAddChild={() => addChildRow(row)}
onDelete={() => handleDelete(row)}
/>
))}
</TableBody>
</Table>
</div>
)}
</section>
</div>
);
}
export { CnstpCodeManagementPage };
@@ -0,0 +1 @@
export { CodeManagementPage } from "./ui/code-management-page";

Some files were not shown because too many files have changed in this diff Show More