[as fr] 한글기안기 변경이력 관리 추가
This commit is contained in:
@@ -22,6 +22,28 @@ async function findLatestByCrtDmndUuid(
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findListByCrtDmndUuid(
|
||||
crtDmndUuid: string,
|
||||
branchSeCd?: string,
|
||||
): Promise<PwrkCnsltnDocCrtRsltResponseDto[]> {
|
||||
const response = await client.get<{ content: PwrkCnsltnDocCrtRsltResponseDto[] } | PwrkCnsltnDocCrtRsltResponseDto[]>(
|
||||
BASE,
|
||||
{
|
||||
params: {
|
||||
crtDmndUuid,
|
||||
branchSeCd: branchSeCd || undefined,
|
||||
page: 0,
|
||||
size: 100,
|
||||
sort: "verSn,desc",
|
||||
},
|
||||
},
|
||||
);
|
||||
if (Array.isArray(response.data)) {
|
||||
return response.data;
|
||||
}
|
||||
return response.data?.content ?? [];
|
||||
}
|
||||
|
||||
async function openWebHwpLatest(crtDmndUuid: string, branchSeCd?: string) {
|
||||
return client.get<Blob>(`${BASE}/${crtDmndUuid}/webhwp/open`, {
|
||||
params: branchSeCd ? { branchSeCd } : undefined,
|
||||
@@ -29,6 +51,17 @@ async function openWebHwpLatest(crtDmndUuid: string, branchSeCd?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
async function openWebHwpByVer(
|
||||
crtDmndUuid: string,
|
||||
verSn: number,
|
||||
branchSeCd?: string,
|
||||
) {
|
||||
return client.get<Blob>(`${BASE}/${crtDmndUuid}/${verSn}/webhwp/open`, {
|
||||
params: branchSeCd ? { branchSeCd } : undefined,
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
|
||||
async function download(crtDmndUuid: string, verSn: number, branchSeCd?: string) {
|
||||
return client.get<Blob>(`${BASE}/${crtDmndUuid}/${verSn}/download`, {
|
||||
params: branchSeCd ? { branchSeCd } : undefined,
|
||||
@@ -83,7 +116,9 @@ async function updateMultipart(
|
||||
|
||||
export const pwrkCnsltnDocCrtRsltService = {
|
||||
findLatestByCrtDmndUuid,
|
||||
findListByCrtDmndUuid,
|
||||
openWebHwpLatest,
|
||||
openWebHwpByVer,
|
||||
download,
|
||||
downloadLatest,
|
||||
saveMultipart,
|
||||
|
||||
+9
@@ -25,7 +25,16 @@ export interface PwrkCnsltnDocCrtRsltService {
|
||||
crtDmndUuid: string,
|
||||
branchSeCd?: string,
|
||||
): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
|
||||
findListByCrtDmndUuid(
|
||||
crtDmndUuid: string,
|
||||
branchSeCd?: string,
|
||||
): Promise<PwrkCnsltnDocCrtRsltResponseDto[]>;
|
||||
openWebHwpLatest(crtDmndUuid: string, branchSeCd?: string): Promise<AxiosResponse<Blob>>;
|
||||
openWebHwpByVer(
|
||||
crtDmndUuid: string,
|
||||
verSn: number,
|
||||
branchSeCd?: string,
|
||||
): Promise<AxiosResponse<Blob>>;
|
||||
download(
|
||||
crtDmndUuid: string,
|
||||
verSn: number,
|
||||
|
||||
@@ -165,6 +165,7 @@ function PwrkCnsltnDocMngDialog({
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedAtchFile[]>([]);
|
||||
const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false);
|
||||
const [isEvlDialogOpen, setIsEvlDialogOpen] = useState(false);
|
||||
const [selectedVerSn, setSelectedVerSn] = useState<number | null>(null);
|
||||
|
||||
// 전자결재 브랜치 목록 직접 조회 (Single Source of Truth)
|
||||
const { data: elaprBranchList = [] } = useElaprBranchesByDocNo(
|
||||
@@ -312,6 +313,7 @@ function PwrkCnsltnDocMngDialog({
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setSelectedVerSn(null);
|
||||
setCurrentBranchSeCd(
|
||||
resolveDefaultBranchSeCd({
|
||||
branchSeCd: effectiveBranchSeCd,
|
||||
@@ -345,6 +347,13 @@ function PwrkCnsltnDocMngDialog({
|
||||
const completedDraft = findCompletedPwrkDraft(pwrkCnsltnData);
|
||||
const crtDmndUuid = completedDraft?.crtDmndUuid;
|
||||
|
||||
// 전체 버전 이력 목록 조회 (verSn 역순 정렬)
|
||||
const { data: historyList = [] } = useQuery({
|
||||
queryKey: ["pwrk-cnsltn-doc-crt-rslt", "history", crtDmndUuid],
|
||||
queryFn: () => pwrkCnsltnDocCrtRsltService.findListByCrtDmndUuid(crtDmndUuid!),
|
||||
enabled: open && Boolean(crtDmndUuid),
|
||||
});
|
||||
|
||||
const targetBranchForApi = isForkJoin ? currentBranchSeCd : undefined;
|
||||
|
||||
const {
|
||||
@@ -352,8 +361,23 @@ function PwrkCnsltnDocMngDialog({
|
||||
isLoading: isDocumentLoading,
|
||||
isError: isDocumentError,
|
||||
} = useQuery({
|
||||
queryKey: ["pwrk-cnsltn-doc-crt-rslt", "open-latest", crtDmndUuid, targetBranchForApi],
|
||||
queryFn: () => pwrkCnsltnDocCrtRsltService.openWebHwpLatest(crtDmndUuid!, targetBranchForApi),
|
||||
queryKey: [
|
||||
"pwrk-cnsltn-doc-crt-rslt",
|
||||
selectedVerSn !== null ? "open-version" : "open-latest",
|
||||
crtDmndUuid,
|
||||
targetBranchForApi,
|
||||
selectedVerSn,
|
||||
],
|
||||
queryFn: () => {
|
||||
if (selectedVerSn !== null) {
|
||||
return pwrkCnsltnDocCrtRsltService.openWebHwpByVer(
|
||||
crtDmndUuid!,
|
||||
selectedVerSn,
|
||||
targetBranchForApi,
|
||||
);
|
||||
}
|
||||
return pwrkCnsltnDocCrtRsltService.openWebHwpLatest(crtDmndUuid!, targetBranchForApi);
|
||||
},
|
||||
select: (response) => response.data,
|
||||
enabled: open && Boolean(crtDmndUuid),
|
||||
});
|
||||
@@ -365,7 +389,8 @@ function PwrkCnsltnDocMngDialog({
|
||||
? "_시설"
|
||||
: "_종합"
|
||||
: "";
|
||||
const documentFileName = `실무협의서${branchLabel}_${pwrkCnsltnData?.pwrkCnsltnDocNo ?? crtDmndUuid}.hwpx`;
|
||||
const verSuffix = selectedVerSn !== null ? `_v${selectedVerSn}` : "";
|
||||
const documentFileName = `실무협의서${branchLabel}${verSuffix}_${pwrkCnsltnData?.pwrkCnsltnDocNo ?? crtDmndUuid}.hwpx`;
|
||||
const isLoading = isPwrkCnsltnLoading || isDocumentLoading;
|
||||
const isError = isPwrkCnsltnError || isDocumentError;
|
||||
const pjtcoAjmtSearchParams = useMemo(
|
||||
@@ -401,6 +426,7 @@ function PwrkCnsltnDocMngDialog({
|
||||
setAtchFileGroupId("");
|
||||
setUploadedFiles([]);
|
||||
setIsEvlDialogOpen(false);
|
||||
setSelectedVerSn(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -463,7 +489,11 @@ function PwrkCnsltnDocMngDialog({
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["pwrk-cnsltn-doc-crt-rslt", "open-latest", completedDraft.crtDmndUuid],
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["pwrk-cnsltn-doc-crt-rslt", "history", completedDraft.crtDmndUuid],
|
||||
}),
|
||||
]);
|
||||
setSelectedVerSn(null);
|
||||
alert(`실무협의서를 저장했습니다. (버전 ${savedDocument.verSn})`);
|
||||
|
||||
if (savedDocument.verSn <= AI_DRAFT_EVL_MAX_VER_SN) {
|
||||
@@ -474,10 +504,17 @@ function PwrkCnsltnDocMngDialog({
|
||||
async function handleDownload() {
|
||||
if (!completedDraft) throw new Error("다운로드할 실무협의서 초안이 없습니다.");
|
||||
|
||||
const response = await pwrkCnsltnDocCrtRsltService.downloadLatest(
|
||||
completedDraft.crtDmndUuid,
|
||||
targetBranchForApi,
|
||||
);
|
||||
const response =
|
||||
selectedVerSn !== null
|
||||
? await pwrkCnsltnDocCrtRsltService.download(
|
||||
completedDraft.crtDmndUuid,
|
||||
selectedVerSn,
|
||||
targetBranchForApi,
|
||||
)
|
||||
: await pwrkCnsltnDocCrtRsltService.downloadLatest(
|
||||
completedDraft.crtDmndUuid,
|
||||
targetBranchForApi,
|
||||
);
|
||||
const objectUrl = window.URL.createObjectURL(response.data);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = objectUrl;
|
||||
@@ -611,6 +648,66 @@ function PwrkCnsltnDocMngDialog({
|
||||
supplementBranches,
|
||||
]);
|
||||
|
||||
// 현재 활성화된 탭(브랜치)의 최신(제일 마지막) verSn 계산
|
||||
const latestVerSnForCurrentBranch = useMemo(() => {
|
||||
const branchItems = historyList.filter(
|
||||
(item) => (item.branchSeCd || "MAIN") === (currentBranchSeCd || "MAIN"),
|
||||
);
|
||||
if (!branchItems.length) return null;
|
||||
return Math.max(...branchItems.map((item) => item.verSn));
|
||||
}, [historyList, currentBranchSeCd]);
|
||||
|
||||
// 최신 버전을 열람 중인지 여부 (null이면 최신 모드이거나, 선택된 버전이 해당 탭의 최댓값과 일치)
|
||||
const isCurrentBranchLatest =
|
||||
selectedVerSn === null || selectedVerSn === latestVerSnForCurrentBranch;
|
||||
|
||||
// 저장 버튼은 해당 탭의 제일 마지막(최신) 버전이 아니면 자동 숨김 처리
|
||||
const canSaveInCurrentView = canSaveCurrentBranch && isCurrentBranchLatest;
|
||||
|
||||
// 변경이력 콤보 박스 컴포넌트
|
||||
const versionHistorySelect = (
|
||||
<div className="flex items-center gap-2">
|
||||
<label htmlFor="pwrk-version-history-select" className="text-sm font-medium text-muted-foreground whitespace-nowrap">
|
||||
변경이력:
|
||||
</label>
|
||||
<select
|
||||
id="pwrk-version-history-select"
|
||||
className="h-8 rounded-md border border-input bg-background px-2 py-1 text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
value={selectedVerSn ?? ""}
|
||||
onChange={(e) => {
|
||||
const val = e.target.value;
|
||||
if (!val) {
|
||||
setSelectedVerSn(null);
|
||||
return;
|
||||
}
|
||||
const num = Number(val);
|
||||
const item = historyList.find((h) => h.verSn === num);
|
||||
if (item) {
|
||||
const targetBranch = item.branchSeCd || "MAIN";
|
||||
setCurrentBranchSeCd(targetBranch);
|
||||
setSelectedVerSn(item.verSn);
|
||||
}
|
||||
}}
|
||||
aria-label="변경이력 선택"
|
||||
>
|
||||
<option value="">선택</option>
|
||||
{historyList.map((item) => {
|
||||
const branchText =
|
||||
item.branchSeCd === "SF"
|
||||
? "[시설]"
|
||||
: item.branchSeCd === "SP"
|
||||
? "[관로]"
|
||||
: "[통합]";
|
||||
return (
|
||||
<option key={`${item.branchSeCd}_${item.verSn}`} value={item.verSn}>
|
||||
{branchText} 버전 {item.verSn}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
|
||||
const editorIframeHeight = isForkJoin
|
||||
? "h-[50vh] min-h-[26rem]"
|
||||
: "h-[58vh] min-h-[32rem]";
|
||||
@@ -642,7 +739,10 @@ function PwrkCnsltnDocMngDialog({
|
||||
type="button"
|
||||
size="sm"
|
||||
color={currentBranchSeCd === "SP" ? "primary" : "white"}
|
||||
onClick={() => setCurrentBranchSeCd("SP")}
|
||||
onClick={() => {
|
||||
setCurrentBranchSeCd("SP");
|
||||
setSelectedVerSn(null);
|
||||
}}
|
||||
>
|
||||
관로 초안 (SP)
|
||||
</Button>
|
||||
@@ -650,7 +750,10 @@ function PwrkCnsltnDocMngDialog({
|
||||
type="button"
|
||||
size="sm"
|
||||
color={currentBranchSeCd === "SF" ? "primary" : "white"}
|
||||
onClick={() => setCurrentBranchSeCd("SF")}
|
||||
onClick={() => {
|
||||
setCurrentBranchSeCd("SF");
|
||||
setSelectedVerSn(null);
|
||||
}}
|
||||
>
|
||||
시설 초안 (SF)
|
||||
</Button>
|
||||
@@ -660,7 +763,10 @@ function PwrkCnsltnDocMngDialog({
|
||||
type="button"
|
||||
size="sm"
|
||||
color={currentBranchSeCd === "MAIN" ? "primary" : "white"}
|
||||
onClick={() => setCurrentBranchSeCd("MAIN")}
|
||||
onClick={() => {
|
||||
setCurrentBranchSeCd("MAIN");
|
||||
setSelectedVerSn(null);
|
||||
}}
|
||||
>
|
||||
종합 실무협의서 (MAIN)
|
||||
</Button>
|
||||
@@ -717,13 +823,14 @@ function PwrkCnsltnDocMngDialog({
|
||||
<div className="min-w-0 flex-1">
|
||||
<HangulGiangi
|
||||
documentBlob={documentBlob}
|
||||
documentKey={`${completedDraft.crtDmndUuid}_${currentBranchSeCd}`}
|
||||
documentKey={`${completedDraft.crtDmndUuid}_${currentBranchSeCd}_${selectedVerSn ?? "latest"}`}
|
||||
documentFileName={documentFileName}
|
||||
canSave={canSaveCurrentBranch}
|
||||
canSave={canSaveInCurrentView}
|
||||
iframeClassName={editorIframeHeight}
|
||||
onSaveDocument={handleSaveDocument}
|
||||
onDownload={handleDownload}
|
||||
onDmndSplmnt={onDmndSplmnt}
|
||||
rightSlot={versionHistorySelect}
|
||||
/>
|
||||
</div>
|
||||
{pjtcoAjmtPage?.content?.length ? (
|
||||
@@ -783,10 +890,10 @@ function PwrkCnsltnDocMngDialog({
|
||||
}))}
|
||||
inputId="pwrk-cnsltn-doc-atch-file-input"
|
||||
isUploadingByKey={isUploading}
|
||||
addDisabled={!canSaveCurrentBranch || isAnyAtchUploading}
|
||||
showAddButton={canSaveCurrentBranch}
|
||||
addDisabled={!canSaveInCurrentView || isAnyAtchUploading}
|
||||
showAddButton={canSaveInCurrentView}
|
||||
onAdd={(files) => void handleAtchFileUpload(files)}
|
||||
onRemove={canSaveCurrentBranch ? (index) => void handleAtchFileRemove(index) : undefined}
|
||||
onRemove={canSaveInCurrentView ? (index) => void handleAtchFileRemove(index) : undefined}
|
||||
/>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
@@ -16,6 +16,7 @@ interface HangulGiangiToolbarProps {
|
||||
onDownload: () => void;
|
||||
onFullscreen: () => void;
|
||||
onDmndSplmnt?: () => void;
|
||||
rightSlot?: React.ReactNode;
|
||||
}
|
||||
|
||||
function HangulGiangiToolbar({
|
||||
@@ -26,11 +27,13 @@ function HangulGiangiToolbar({
|
||||
onDownload,
|
||||
onFullscreen,
|
||||
onDmndSplmnt,
|
||||
rightSlot,
|
||||
}: HangulGiangiToolbarProps) {
|
||||
const openInputId = useId();
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-nowrap items-center gap-2 border-b border-border bg-background p-3">
|
||||
<div className="flex w-full flex-wrap items-center justify-between gap-2 border-b border-border bg-background p-3">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{WEBHYP_DEV_MODE && (
|
||||
<label
|
||||
htmlFor={openInputId}
|
||||
@@ -87,6 +90,8 @@ function HangulGiangiToolbar({
|
||||
보완요청
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{rightSlot && <div className="flex items-center gap-2">{rightSlot}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ function HangulGiangi({
|
||||
className,
|
||||
iframeClassName,
|
||||
onReady,
|
||||
rightSlot,
|
||||
}: HangulGiangiProps) {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const openedDocumentKeyRef = useRef<string | null>(null);
|
||||
@@ -209,6 +210,7 @@ function HangulGiangi({
|
||||
onDownload={handleDownload}
|
||||
onFullscreen={handleFullscreen}
|
||||
onDmndSplmnt={onDmndSplmnt}
|
||||
rightSlot={rightSlot}
|
||||
/>
|
||||
|
||||
<iframe
|
||||
@@ -329,6 +331,7 @@ interface HangulGiangiProps {
|
||||
className?: string;
|
||||
iframeClassName?: string;
|
||||
onReady?: () => void;
|
||||
rightSlot?: React.ReactNode;
|
||||
}
|
||||
|
||||
export { HangulGiangi };
|
||||
|
||||
Reference in New Issue
Block a user