Merge branch 'iipbs/as/dev'
This commit is contained in:
@@ -6,3 +6,7 @@ export {
|
||||
} from "./api/queries";
|
||||
export { parseFilenameFromResponse } from "./lib/parse-filename-from-response";
|
||||
export type { Attach, AttachService } from "./model/attach-interface";
|
||||
export {
|
||||
AttachmentDownloadLink,
|
||||
type AttachmentDownloadLinkProps,
|
||||
} from "./ui/attachment-download-link";
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"use client";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { cn } from "@common/components";
|
||||
|
||||
import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "../api/queries";
|
||||
|
||||
interface AttachmentDownloadLinkProps {
|
||||
atflCd?: string;
|
||||
atflId?: string;
|
||||
fileName?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function AttachmentDownloadLink({
|
||||
atflCd,
|
||||
atflId,
|
||||
fileName,
|
||||
className,
|
||||
}: AttachmentDownloadLinkProps) {
|
||||
const downloadByAtflCd = useDownloadAttachmentByAtflCd();
|
||||
const downloadByAtflId = useDownloadAttachmentByAtflId();
|
||||
const displayName = fileName || "첨부파일";
|
||||
const canDownload = Boolean(atflId || atflCd);
|
||||
const isPending = atflId ? downloadByAtflId.isPending : downloadByAtflCd.isPending;
|
||||
|
||||
function handleClick() {
|
||||
if (atflId) {
|
||||
downloadByAtflId.mutate(atflId);
|
||||
return;
|
||||
}
|
||||
if (atflCd) downloadByAtflCd.mutate(atflCd);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline disabled:cursor-wait disabled:opacity-60",
|
||||
className,
|
||||
)}
|
||||
aria-label={`${displayName} 다운로드`}
|
||||
disabled={isPending || !canDownload}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{displayName}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export { AttachmentDownloadLink };
|
||||
export type { AttachmentDownloadLinkProps };
|
||||
@@ -90,6 +90,7 @@ function ElaprAplnPickerGrid({
|
||||
? isExternalLoading
|
||||
: aplnQuery.isPending && Boolean(elaprTmpltId);
|
||||
const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined;
|
||||
const excludedVisibleRows = visibleRows.filter((r) => r.atrzSeComCd !== "ATRZ01");
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
@@ -149,7 +150,7 @@ function ElaprAplnPickerGrid({
|
||||
</div>
|
||||
) : !isReady ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">결재서식을 선택하세요.</p>
|
||||
) : visibleRows.length === 0 ? (
|
||||
) : excludedVisibleRows.length === 0 ? (
|
||||
<p className="py-16 text-center text-lg text-muted-foreground">
|
||||
등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요.
|
||||
</p>
|
||||
@@ -174,10 +175,10 @@ function ElaprAplnPickerGrid({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<SortableContext
|
||||
items={visibleRows.map((r) => r.rowKey)}
|
||||
items={excludedVisibleRows.map((r) => r.rowKey)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{visibleRows.map((row, index) => (
|
||||
{excludedVisibleRows.map((row, index) => (
|
||||
<SortableAplnPickerRow
|
||||
key={row.rowKey}
|
||||
row={row}
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn";
|
||||
import {
|
||||
Button,
|
||||
@@ -319,15 +316,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import type { EsntlAtchFileMng } from "@/entities/esntl";
|
||||
import {
|
||||
@@ -61,6 +61,7 @@ function EsntlAtchFileFormDialog({
|
||||
const formId = useId();
|
||||
const atchFileGroupIdRef = useRef("");
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedTemplateFile[]>([]);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { register, handleSubmit, reset, setValue, watch } = useForm<EsntlAtchFileFormValues>({
|
||||
defaultValues: {
|
||||
@@ -219,6 +220,9 @@ function EsntlAtchFileFormDialog({
|
||||
files={uploadedFiles.map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={fileInputId}
|
||||
isUploadingByKey={isUploading}
|
||||
|
||||
@@ -6,6 +6,7 @@ export const FNRS_CNSLTN_VIEW_BUTTON_ACTION = {
|
||||
rjct: "rjct",
|
||||
sncnAprv: "sncnAprv",
|
||||
rvwRply: "rvwRply",
|
||||
rvwOpnnRply: "rvwOpnnRply",
|
||||
rqstTechRvw: "rqstTechRvw",
|
||||
fileSplmnt: "fileSplmnt",
|
||||
dsgntRvwr: "dsgntRvwr",
|
||||
@@ -37,6 +38,7 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, FnrsCnsltnViewBu
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.del,
|
||||
],
|
||||
RB: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, FNRS_CNSLTN_VIEW_BUTTON_ACTION.actnCmptn],
|
||||
RA: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
TA: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
ROLE_USER04: {
|
||||
@@ -45,7 +47,11 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, FnrsCnsltnViewBu
|
||||
},
|
||||
ROLE_USER03: {
|
||||
LE: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw],
|
||||
TP: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply],
|
||||
TP: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply,
|
||||
],
|
||||
RA: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw,
|
||||
@@ -54,7 +60,7 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, FnrsCnsltnViewBu
|
||||
},
|
||||
};
|
||||
|
||||
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "EV", "TE", "RK"] as const;
|
||||
const ROLE_USER01_ATRZ_STATUS_CODES = ["ER", "EV", "SL", "TE", "RK"] as const;
|
||||
|
||||
const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
|
||||
string,
|
||||
@@ -63,8 +69,11 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
|
||||
ER: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt, FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr],
|
||||
},
|
||||
EV: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr],
|
||||
SL: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
TE: {
|
||||
ATRZ02: [
|
||||
@@ -79,7 +88,13 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record<
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv,
|
||||
],
|
||||
ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply],
|
||||
ATRZ99: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply,
|
||||
],
|
||||
},
|
||||
EV: {
|
||||
ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr],
|
||||
},
|
||||
RK: {
|
||||
ATRZ02: [
|
||||
@@ -143,7 +158,7 @@ function resolveRoleUser01AtrzButtonActions(
|
||||
if (needsPendingApproval && options?.hasPendingApprovalRequest === false) return false;
|
||||
|
||||
if (options?.hasCompletedDraft != null) {
|
||||
if (prgrsSttsComCd === "TE" || prgrsSttsComCd === "RK") {
|
||||
if (prgrsSttsComCd === "SL" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "RK") {
|
||||
if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng) {
|
||||
return options.hasCompletedDraft;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ function FloatingActionButtons({
|
||||
|
||||
const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd);
|
||||
const needsElaprPrgrsCheck = needsAtrzResolve && ["TE", "RK"].includes(prgrsSttsComCd);
|
||||
const needsPwrkDraftCheck = dtlInstCd === "ROLE_USER01" && ["TE", "RK"].includes(prgrsSttsComCd);
|
||||
const needsPwrkDraftCheck =
|
||||
dtlInstCd === "ROLE_USER01" && ["SL", "TE", "RK"].includes(prgrsSttsComCd);
|
||||
const needsPwrkForRb = dtlInstCd === "ROLE_USER05" && prgrsSttsComCd === "RB";
|
||||
const needsPwrkLookup = needsPwrkDraftCheck || needsPwrkForRb;
|
||||
|
||||
@@ -137,6 +138,7 @@ function FloatingActionButtons({
|
||||
elaprPrgrsForPending,
|
||||
userInfo?.userUnqId,
|
||||
);
|
||||
const viewOnlyPwrkCnsltnDoc = prgrsSttsComCd === "SL";
|
||||
|
||||
const actionButtons = useMemo(() => {
|
||||
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
|
||||
@@ -317,12 +319,11 @@ function FloatingActionButtons({
|
||||
return;
|
||||
}
|
||||
|
||||
const isResultReply = prgrsSttsComCd === "RR";
|
||||
const payload = buildTechReviewReplyPayload(
|
||||
data,
|
||||
checklistFormState,
|
||||
"ENV",
|
||||
isResultReply ? "EP" : "RB",
|
||||
"EP",
|
||||
"CP",
|
||||
getReviewOpinionFormData(),
|
||||
opnrNm,
|
||||
@@ -336,6 +337,36 @@ function FloatingActionButtons({
|
||||
techReviewOpinionRef?.current?.resetForm();
|
||||
}
|
||||
|
||||
async function handleRvwOpnnRplyClick() {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
data.cklstResultList ?? [],
|
||||
checklistFormState.results,
|
||||
"ENV",
|
||||
);
|
||||
|
||||
if (percent !== 100) {
|
||||
alert("모든 체크리스트 항목을 체크해야 검토 의견 회신이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildTechReviewReplyPayload(
|
||||
data,
|
||||
checklistFormState,
|
||||
"ENV",
|
||||
"RB",
|
||||
"CP",
|
||||
getReviewOpinionFormData(),
|
||||
opnrNm,
|
||||
);
|
||||
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply,
|
||||
request: payload,
|
||||
successMessage: "검토 의견 회신이 완료되었습니다.",
|
||||
});
|
||||
techReviewOpinionRef?.current?.resetForm();
|
||||
}
|
||||
|
||||
async function handleRqstTechRvwClick() {
|
||||
if (prgrsSttsComCd === "LE") {
|
||||
const { percent } = computeReviewStepChecklistProgress(
|
||||
@@ -365,7 +396,7 @@ function FloatingActionButtons({
|
||||
await executeAction({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw,
|
||||
request: payload,
|
||||
successMessage: "조치결과 검토의뢰가 완료되었습니다.",
|
||||
successMessage: "조치 결과 검토 의뢰가 완료되었습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -566,10 +597,15 @@ function FloatingActionButtons({
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv) && (
|
||||
<Button onClick={handleSncnAprvClick} disabled={isProcessing}>
|
||||
<Button color="green" onClick={handleSncnAprvClick} disabled={isProcessing}>
|
||||
결과 회신
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply) && (
|
||||
<Button onClick={handleRvwOpnnRplyClick} disabled={isProcessing}>
|
||||
검토 의견 회신
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply) && (
|
||||
<Button color="green" onClick={handleRvwRplyClick} disabled={isProcessing}>
|
||||
검토 결과 회신
|
||||
@@ -582,7 +618,7 @@ function FloatingActionButtons({
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw) && (
|
||||
<Button onClick={handleRqstActnRvwClick} disabled={isProcessing}>
|
||||
조치결과 검토 의뢰
|
||||
조치 결과 검토 의뢰
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt) && (
|
||||
@@ -698,7 +734,11 @@ function FloatingActionButtons({
|
||||
fnrsCnsltnDocNo={docNo}
|
||||
atrzSeComCdList={atrzSeComCdList}
|
||||
prgrsSttsComCd={prgrsSttsComCd}
|
||||
canSave={needsElaprPrgrsCheck && !hasPendingApprovalRequest ? false : undefined}
|
||||
canSave={
|
||||
(needsElaprPrgrsCheck && !hasPendingApprovalRequest) || viewOnlyPwrkCnsltnDoc
|
||||
? false
|
||||
: null
|
||||
}
|
||||
onClose={() => setActiveDialog(null)}
|
||||
/>
|
||||
|
||||
@@ -837,6 +877,7 @@ const buttonActionHandlers: Record<FnrsCnsltnViewButtonAction, ActionHandler> =
|
||||
rjct: handleChangePrgrsSttsComCd,
|
||||
sncnAprv: handleChangePrgrsSttsComCd,
|
||||
rvwRply: handleTechReview,
|
||||
rvwOpnnRply: handleTechReview,
|
||||
rqstTechRvw: handleRequestExamine,
|
||||
fileSplmnt: handleDmndSplmnt,
|
||||
dsgntRvwr: handleDsgntRvwr,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { PanelRightClose, PanelRightOpen } from "lucide-react";
|
||||
|
||||
import { getDefaultFileKey, useAlertStore, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { attachService, parseFilenameFromResponse } from "@/entities/attach";
|
||||
import { attachService, parseFilenameFromResponse, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import {
|
||||
pwrkCnsltnDocCrtRsltSaveRequestSchema,
|
||||
pwrkCnsltnDocCrtRsltService,
|
||||
@@ -43,14 +43,14 @@ const PWRK_ATCH_FILE_KEY_PREFIX = "pwrk-cnsltn-doc::";
|
||||
|
||||
const APPROVER_ATRZ_SE_COM_CDS = new Set(["ATRZ03", "ATRZ99"]);
|
||||
|
||||
const VIEW_ONLY_PRGRS_STTS_COM_CDS = new Set(["RA", "RR", "EP", "TA"]);
|
||||
const VIEW_ONLY_PRGRS_STTS_COM_CDS = new Set(["TP", "RA", "RR", "EP", "TA"]);
|
||||
|
||||
interface PwrkCnsltnDocMngDialogProps {
|
||||
open: boolean;
|
||||
fnrsCnsltnDocNo: string;
|
||||
atrzSeComCdList?: string[];
|
||||
prgrsSttsComCd?: string;
|
||||
canSave?: boolean;
|
||||
canSave?: boolean | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ function PwrkCnsltnDocMngDialog({
|
||||
const [atchFileGroupId, setAtchFileGroupId] = useState("");
|
||||
const [uploadedFiles, setUploadedFiles] = useState<UploadedAtchFile[]>([]);
|
||||
const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const {
|
||||
data: pwrkCnsltnData,
|
||||
@@ -344,6 +345,9 @@ function PwrkCnsltnDocMngDialog({
|
||||
files={uploadedFiles.map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId="pwrk-cnsltn-doc-atch-file-input"
|
||||
isUploadingByKey={isUploading}
|
||||
|
||||
@@ -43,11 +43,21 @@ function PwrkCnsltnDocPjtcoAjmtTable({
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="30%" />
|
||||
<col width="50%" />
|
||||
<col width="20%" />
|
||||
</colgroup>
|
||||
<TableHeader className="sticky top-0">
|
||||
<TableRow>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>검증결과</TableHead>
|
||||
<TableHead className="text-right">검증금액</TableHead>
|
||||
<TableHead className="text-right">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span>검증금액</span>
|
||||
<span className="text-sm">(단위: 백만 원)</span>
|
||||
</div>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -332,24 +332,26 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={form.handleSubmit(onSplmntCmptn)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
보완 완료
|
||||
</Button>
|
||||
<>
|
||||
{fnrsCnsltnDocNo ? (
|
||||
<Button
|
||||
color="orange"
|
||||
type="button"
|
||||
onClick={() => setIsPwrkCnsltnDocMngOpen(true)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
실무협의서 관리
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
type="button"
|
||||
onClick={form.handleSubmit(onSplmntCmptn)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
보완 완료
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{isSplmntPrgrsStts && fnrsCnsltnDocNo ? (
|
||||
<Button
|
||||
color="orange"
|
||||
type="button"
|
||||
onClick={() => setIsPwrkCnsltnDocMngOpen(true)}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
실무협의서 관리
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import {
|
||||
AttachmentDownloadLink,
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import {
|
||||
RadioGroup,
|
||||
@@ -82,6 +83,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
opnrSeComCd: editableReviewStep ?? "",
|
||||
});
|
||||
const [uploadedFile, setUploadedFile] = useState<UploadedOpinionFile | null>(null);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
useEffect(() => {
|
||||
setFormData((prev) => ({
|
||||
@@ -246,7 +248,15 @@ const TechReviewOpinionSection = forwardRef<
|
||||
<AttachmentFileField
|
||||
files={
|
||||
uploadedFile
|
||||
? [{ fileKey: uploadedFile.fileKey, fileName: uploadedFile.fileName }]
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
inputId={`rvw-opnn-${reviewStep}-file-input`}
|
||||
@@ -320,15 +330,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { InstlSncnRvwOpnnResponseDto, ReviewStepCode } from "@/entities/instl-sncn";
|
||||
import {
|
||||
Button,
|
||||
@@ -331,15 +328,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { PROXY_HOST } from "@/shared/config";
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field, FieldLabel, FieldSet } from "@/shared/ui/field";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService } from "@/entities/attach";
|
||||
import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan";
|
||||
import {
|
||||
Button,
|
||||
@@ -338,15 +335,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.atchFileInfo?.atflCd ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline"
|
||||
href={`${PROXY_HOST}/attach-service/api/v1/atflcd/download/${item.atchFileInfo.atflCd}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileInfo.atflCd}
|
||||
fileName={item.atchFileInfo.orgnlFileNm || "다운로드"}
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink } from "@/entities/attach";
|
||||
import { useFacilityList } from "@/entities/bns-if";
|
||||
import { useNowDmgMtncGnsvy } from "@/entities/dc";
|
||||
import type {
|
||||
@@ -176,7 +174,6 @@ function TmplntFacilityMatrix({
|
||||
}
|
||||
|
||||
function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd();
|
||||
const [selectedCode, setSelectedCode] = useState(tmplntList[0]?.swtrmtFcltCd ?? "");
|
||||
const { data: facilityListData } = useFacilityList(
|
||||
{ sido: det.sido ?? "", gugun: det.gugun ?? "" },
|
||||
@@ -261,13 +258,11 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
<TableCell>{facilityName} 계획평면도</TableCell>
|
||||
<TableCell>
|
||||
{item.atchFileGroupId ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(item.atchFileGroupId ?? "")}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0" />
|
||||
{item.atchFileInfo?.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.atchFileGroupId}
|
||||
fileName={item.atchFileInfo?.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { UploadedFileChip } from "./uploaded-file-chip";
|
||||
interface AttachmentFileFieldItem {
|
||||
fileKey: string;
|
||||
fileName: string;
|
||||
onDownload?: () => void;
|
||||
}
|
||||
|
||||
interface AttachmentFileFieldProps {
|
||||
@@ -41,6 +42,7 @@ function AttachmentFileField({
|
||||
fileName={file.fileName}
|
||||
isUploading={isUploadingByKey[file.fileKey] ?? false}
|
||||
onRemove={onRemove ? () => onRemove(index) : undefined}
|
||||
onDownload={file.onDownload}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -8,15 +8,36 @@ interface UploadedFileChipProps {
|
||||
fileName: string;
|
||||
isUploading?: boolean;
|
||||
onRemove?: () => void;
|
||||
onDownload?: () => void;
|
||||
}
|
||||
|
||||
function UploadedFileChip({ fileName, isUploading = false, onRemove }: UploadedFileChipProps) {
|
||||
function UploadedFileChip({
|
||||
fileName,
|
||||
isUploading = false,
|
||||
onRemove,
|
||||
onDownload,
|
||||
}: UploadedFileChipProps) {
|
||||
const canDownload = Boolean(onDownload) && !isUploading;
|
||||
|
||||
return (
|
||||
<div className="inline-flex items-center justify-between gap-2 rounded-sm border bg-accent p-2">
|
||||
<div className="inline-flex items-center justify-between gap-2 rounded-sm border bg-accent px-3 py-2">
|
||||
<Paperclip className="size-5 shrink-0" />
|
||||
<TextShimmer className="text-xl" active={isUploading}>
|
||||
{fileName}
|
||||
</TextShimmer>
|
||||
{canDownload ? (
|
||||
<button
|
||||
type="button"
|
||||
className="cursor-pointer text-left hover:underline"
|
||||
aria-label={`${fileName} 다운로드`}
|
||||
onClick={onDownload}
|
||||
>
|
||||
<TextShimmer className="text-xl" active={isUploading}>
|
||||
{fileName}
|
||||
</TextShimmer>
|
||||
</button>
|
||||
) : (
|
||||
<TextShimmer className="text-xl" active={isUploading}>
|
||||
{fileName}
|
||||
</TextShimmer>
|
||||
)}
|
||||
{onRemove ? (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
SectionCollapsible,
|
||||
UploadedFileChip,
|
||||
} from "@/shared/ui";
|
||||
import { attachService, useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import { attachService, useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
Button,
|
||||
@@ -217,6 +217,7 @@ function AttachmentsFormSection({
|
||||
{ docSeComCd },
|
||||
);
|
||||
const { mutate: downloadTemplate } = useDownloadAttachmentByAtflCd();
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
async function invalidateAttachmentQueries() {
|
||||
await queryClient.invalidateQueries({
|
||||
@@ -431,6 +432,9 @@ function AttachmentsFormSection({
|
||||
files={(uploadedFiles[file.esntlAtchFileId] ?? []).map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.file?.name ?? item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={`${file.esntlAtchFileId}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
@@ -470,6 +474,9 @@ function AttachmentsFormSection({
|
||||
fileName={item.file?.name ?? item.fileName}
|
||||
isUploading={isUploading[item.fileKey] ?? false}
|
||||
onRemove={() => void removeEtcFile(idx)}
|
||||
onDownload={
|
||||
item.atflId ? () => downloadByAtflId(item.atflId!) : undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { formatFileSize } from "@/shared/lib/file-utils";
|
||||
import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto } from "@/shared/model";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink } from "@/entities/attach";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
|
||||
interface AttachmentsViewSectionProps {
|
||||
@@ -14,9 +12,6 @@ interface AttachmentsViewSectionProps {
|
||||
}
|
||||
|
||||
function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewSectionProps) {
|
||||
const { mutate: downloadByAtflCd } = useDownloadAttachmentByAtflCd();
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
return (
|
||||
<>
|
||||
{esntlFileList.length > 0 && (
|
||||
@@ -48,14 +43,12 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{files.map((file) => (
|
||||
<a
|
||||
<AttachmentDownloadLink
|
||||
key={file.atflId}
|
||||
className="flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadByAtflId(file.atflId)}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0 text-blue-600" />
|
||||
{file.orgnlFileNm || "다운로드"}
|
||||
</a>
|
||||
atflId={file.atflId}
|
||||
fileName={file.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -95,13 +88,11 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS
|
||||
<TableRow key={file.atflId}>
|
||||
<TableCell className="text-left">
|
||||
{file.atflCd ? (
|
||||
<a
|
||||
className="flex cursor-pointer items-center gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadByAtflCd(file.atflCd)}
|
||||
>
|
||||
<Paperclip className="size-5 shrink-0 text-blue-600" />
|
||||
{file.orgnlFileNm || file.physFileNm || file.atflId}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={file.atflCd}
|
||||
fileName={file.orgnlFileNm || file.physFileNm || file.atflId}
|
||||
className="items-center"
|
||||
/>
|
||||
) : (
|
||||
<span>{file.orgnlFileNm || file.physFileNm || file.atflId}</span>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useEffect, useId, useState } from "react";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField } from "@/shared/ui";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import {
|
||||
Button,
|
||||
@@ -96,6 +96,7 @@ function SplmntDmndDialog({
|
||||
}: SplmntDmndDialogProps) {
|
||||
const formId = useId();
|
||||
const [rows, setRows] = useState<SplmntDmndRow[]>([createEmptyRow()]);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
@@ -289,6 +290,9 @@ function SplmntDmndDialog({
|
||||
files={row.uploadedFiles.map((file) => ({
|
||||
fileKey: file.fileKey,
|
||||
fileName: file.fileName,
|
||||
onDownload: file.atflId
|
||||
? () => downloadByAtflId(file.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={fileInputId}
|
||||
isUploadingByKey={isUploading}
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { attachService } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink, attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import {
|
||||
Badge,
|
||||
Table,
|
||||
@@ -75,6 +73,7 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
|
||||
const initialFilesMappedRef = useRef(false);
|
||||
const isMountedRef = useRef(true);
|
||||
const atflCdByRowRef = useRef<Record<string, string>>({});
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { uploadFileAsync, isUploading, uploads } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
@@ -274,10 +273,10 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
|
||||
<div className="space-y-2">
|
||||
<div className="whitespace-pre-wrap">{item.splmntDmndCn || "-"}</div>
|
||||
{item.splmntDmndAtchFileGroupId ? (
|
||||
<span className="inline-flex items-start gap-1">
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{item.splmntDmndAtchFileNm || "첨부파일"}
|
||||
</span>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.splmntDmndAtchFileGroupId}
|
||||
fileName={item.splmntDmndAtchFileNm}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -308,6 +307,9 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
|
||||
files={rowFiles.map((file) => ({
|
||||
fileKey: file.fileKey,
|
||||
fileName: file.fileName,
|
||||
onDownload: file.atflId
|
||||
? () => downloadByAtflId(file.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={fileInputId}
|
||||
isUploadingByKey={isUploading}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
import { dayjs } from "@/shared/lib";
|
||||
import type { SplmntDmndResponseExtension, SplmntDmndSaveBase } from "@/shared/model";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { useDownloadAttachmentByAtflCd } from "@/entities/attach";
|
||||
import { AttachmentDownloadLink } from "@/entities/attach";
|
||||
import {
|
||||
Badge,
|
||||
Table,
|
||||
@@ -23,8 +21,6 @@ interface SupplementDemandSectionProps {
|
||||
}
|
||||
|
||||
function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProps) {
|
||||
const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd();
|
||||
|
||||
if (splmntDmndList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -70,13 +66,10 @@ function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProp
|
||||
<div className="space-y-2">
|
||||
<div className="whitespace-pre-wrap">{item.splmntDmndCn || "-"}</div>
|
||||
{item.splmntDmndAtchFileGroupId ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(item.splmntDmndAtchFileGroupId!)}
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{dmndAtchFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.splmntDmndAtchFileGroupId}
|
||||
fileName={dmndAtchFileNm || "다운로드"}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -89,13 +82,10 @@ function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProp
|
||||
<div className="space-y-2">
|
||||
<div className="whitespace-pre-wrap">{item.splmntCn || "-"}</div>
|
||||
{item.splmntAtchFileGroupId ? (
|
||||
<a
|
||||
className="inline-flex cursor-pointer items-start gap-1 text-blue-600 hover:underline"
|
||||
onClick={() => downloadAttachment(item.splmntAtchFileGroupId!)}
|
||||
>
|
||||
<Paperclip className="my-1 size-5 shrink-0" />
|
||||
{cmptnAtchFileNm || "다운로드"}
|
||||
</a>
|
||||
<AttachmentDownloadLink
|
||||
atflCd={item.splmntAtchFileGroupId}
|
||||
fileName={cmptnAtchFileNm || "다운로드"}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user