[as] feat: 재원협의 수정
This commit is contained in:
@@ -51,7 +51,6 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, FnrsCnsltnViewBu
|
||||
LE: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw],
|
||||
TP: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply,
|
||||
],
|
||||
RA: [
|
||||
|
||||
@@ -174,6 +174,11 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
async function onApply(data: FnrsCnsltnRegistFormData) {
|
||||
if (isSubmitting) return;
|
||||
|
||||
if (data.privacyConsent !== "Y") {
|
||||
alert("개인정보 수집/이용에 동의해야 신청이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFormChecklistComplete(data.cklstResultList)) {
|
||||
alert("모든 체크리스트 항목을 체크해야 신청이 가능합니다.");
|
||||
return;
|
||||
|
||||
@@ -4,15 +4,14 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import type { AtflMngClientResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se";
|
||||
import { useFnrsSharSeMngTree } from "@/entities/fnrs-shar-se";
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Input,
|
||||
Skeleton,
|
||||
@@ -37,6 +36,7 @@ import { calcDiffPercent } from "../lib/format-utils";
|
||||
import { BizCstTotalDisplayTable } from "./biz-cst-total-display-table";
|
||||
|
||||
const SHAR_FILE_KEY_PREFIX = "fnrs-shar::";
|
||||
const SHAR_FILE_ACCEPT = ".hwp,.hwpx";
|
||||
|
||||
interface FnrsSharFormData {
|
||||
fnrsSharList: FnrsSharListItem[];
|
||||
@@ -53,6 +53,12 @@ interface FnrsSharFormData {
|
||||
|
||||
type SharFieldKey = "exstnAmt" | "chgRqtAmt" | "rmrkCn";
|
||||
|
||||
interface SharUploadedFile {
|
||||
fileKey: string;
|
||||
fileName: string;
|
||||
atflId?: string;
|
||||
}
|
||||
|
||||
interface FnrsSharFormSectionProps {
|
||||
initialSharTree?: FnrsSharBizCstTreeDto[];
|
||||
initialSharAtchFiles?: AtflMngClientResponseDto[];
|
||||
@@ -62,12 +68,23 @@ function createSharUploadKey(file: File) {
|
||||
return `${SHAR_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function toInitialSharFile(file?: AtflMngClientResponseDto): SharUploadedFile | null {
|
||||
if (!file?.orgnlFileNm) return null;
|
||||
|
||||
return {
|
||||
fileKey: file.atflId ? `server::${file.atflId}` : `${SHAR_FILE_KEY_PREFIX}${file.atflCd}`,
|
||||
fileName: file.orgnlFileNm,
|
||||
atflId: file.atflId,
|
||||
};
|
||||
}
|
||||
|
||||
function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsSharFormSectionProps) {
|
||||
const { control, setValue, getValues } = useFormContext<FnrsSharFormData>();
|
||||
const fnrsSharList = useWatch({ control, name: "fnrsSharList" }) ?? [];
|
||||
const [uploadedFileName, setUploadedFileName] = useState(
|
||||
() => initialSharAtchFiles?.[0]?.orgnlFileNm ?? "",
|
||||
const [uploadedFile, setUploadedFile] = useState<SharUploadedFile | null>(() =>
|
||||
toInitialSharFile(initialSharAtchFiles?.[0]),
|
||||
);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { data: masterTree = [], isLoading: isMasterLoading } = useFnrsSharSeMngTree();
|
||||
const { data: acntgTree = [] } = useAcntgSeMngTree();
|
||||
@@ -78,9 +95,13 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
const isLoading = initialSharTree?.length ? false : isMasterLoading;
|
||||
|
||||
const { uploadFileAsync, isUploading } = useTus({
|
||||
onSuccess: (atflCd) => {
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
if (!atflCd) return;
|
||||
setValue("fnrsCnsltnMng.sharAtchFileGroupId", atflCd, { shouldDirty: true });
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
const isSharUploading = Object.values(isUploading).some(Boolean);
|
||||
@@ -120,16 +141,32 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
applySharList(draft);
|
||||
}
|
||||
|
||||
async function handleSharFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const file = event.target.files?.[0];
|
||||
async function handleSharFileAdd(files: FileList | null) {
|
||||
const file = files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const fileKey = createSharUploadKey(file);
|
||||
setUploadedFileName(file.name);
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
event.target.value = "";
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
const sharAtchFiles = uploadedFile
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionCollapsible title="재원분담">
|
||||
@@ -257,31 +294,20 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
|
||||
<div className="mt-6 px-2 pb-2">
|
||||
<h3 className="mb-2 text-xl font-semibold">첨부파일</h3>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{isSharUploading && uploadedFileName ? (
|
||||
<TextShimmer className="max-w-lg text-xl" active>
|
||||
{uploadedFileName}
|
||||
</TextShimmer>
|
||||
) : (
|
||||
<Input
|
||||
value={uploadedFileName}
|
||||
readOnly
|
||||
placeholder="총사업비 신청서(.hwp) 파일을 등록해주세요."
|
||||
className="max-w-lg rounded-sm"
|
||||
/>
|
||||
)}
|
||||
<Button type="button" color="white" disabled={isSharUploading} asChild>
|
||||
<label className="cursor-pointer">
|
||||
첨부파일
|
||||
<input
|
||||
type="file"
|
||||
accept=".hwp,.hwpx"
|
||||
className="hidden"
|
||||
onChange={handleSharFileChange}
|
||||
/>
|
||||
</label>
|
||||
</Button>
|
||||
</div>
|
||||
{!uploadedFile ? (
|
||||
<p className="mb-2 text-lg text-muted-foreground">
|
||||
총사업비 신청서(.hwp) 파일을 등록해주세요.
|
||||
</p>
|
||||
) : null}
|
||||
<AttachmentFileField
|
||||
files={sharAtchFiles}
|
||||
inputId="fnrs-shar-atch-file-input"
|
||||
isUploadingByKey={isUploading}
|
||||
accept={SHAR_FILE_ACCEPT}
|
||||
multiple={false}
|
||||
addDisabled={isSharUploading}
|
||||
onAdd={(fileList) => void handleSharFileAdd(fileList)}
|
||||
/>
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user