feat: 게시물 파일 첨부 최대 갯수 및 크기 제한 작업
This commit is contained in:
@@ -12,6 +12,7 @@ export interface PostParams extends PageableParams {
|
||||
|
||||
export interface Post {
|
||||
bbsId: number;
|
||||
rowNo: number;
|
||||
pstNo: number;
|
||||
pstTtl: string;
|
||||
inqNocs: number;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { PostForm } from "@/widgets/post";
|
||||
import { PostApi, postFormSchema, type PostFormData } from "@/entities/post";
|
||||
import { setCookie, useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
|
||||
function CreatePage({ bbsId }: { bbsId: string }) {
|
||||
const router = useRouter();
|
||||
@@ -34,10 +33,6 @@ function CreatePage({ bbsId }: { bbsId: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCookie("atfl-cd-key", "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
type PostUpdateFormData,
|
||||
} from "@/entities/post";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { setCookie, useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
|
||||
function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -39,10 +38,6 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCookie("atfl-cd-key", "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -6,9 +6,8 @@ import type { Post } from "@/entities/post";
|
||||
|
||||
export const columns: ColumnDef<Post>[] = [
|
||||
{
|
||||
id: "no",
|
||||
accessorKey: "rowNo",
|
||||
header: "NO",
|
||||
cell: ({ row }) => row.index + 1,
|
||||
},
|
||||
{
|
||||
accessorKey: "pstTtl",
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { PostForm } from "@/widgets/post";
|
||||
import { PostApi, postFormSchema, type PostFormData } from "@/entities/post";
|
||||
import { setCookie, useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
|
||||
function CreatePage({ bbsId }: { bbsId: string }) {
|
||||
const router = useRouter();
|
||||
@@ -34,10 +33,6 @@ function CreatePage({ bbsId }: { bbsId: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCookie("atfl-cd-key", "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
type PostUpdateFormData,
|
||||
} from "@/entities/post";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { setCookie, useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
|
||||
function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -39,10 +38,6 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCookie("atfl-cd-key", "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { Post } from "@/entities/post";
|
||||
|
||||
export const columns: ColumnDef<Post & { isUpndFixPost?: boolean }>[] = [
|
||||
{
|
||||
id: "no",
|
||||
accessorKey: "rowNo",
|
||||
header: "NO",
|
||||
cell: ({ row }) => {
|
||||
if (row.original.isUpndFixPost) {
|
||||
@@ -19,7 +19,7 @@ export const columns: ColumnDef<Post & { isUpndFixPost?: boolean }>[] = [
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return row.index + 1;
|
||||
return row.original.rowNo;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,10 +24,10 @@ import { handleTusUpload } from "@/entities/tus";
|
||||
import { BOARD_POSTING_CODE, type Board } from "@/entities/board";
|
||||
import { AttachmentApi, type Attachment } from "@/entities/attachment";
|
||||
import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post";
|
||||
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
|
||||
import { FormWrapper } from "@/shared/ui/form-wrapper";
|
||||
import { useUserStore } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useUserStore, useAlertStore, setCookie } from "@/shared/lib";
|
||||
import { FormWrapper } from "@/shared/ui/form-wrapper";
|
||||
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
|
||||
|
||||
type PostFormProps<T extends PostFormData | PostUpdateFormData> = {
|
||||
form: UseFormReturn<T>;
|
||||
@@ -41,6 +41,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
const atflCd = form.getValues("atflCd" as FieldPath<T>);
|
||||
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [storedBbsInfo] = useLocalStorage("bbs-info", "");
|
||||
|
||||
const [bbsInfo, setBbsInfo] = useState<Board | null>(null);
|
||||
@@ -80,6 +81,29 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const atflCd = form.getValues("atflCd" as FieldPath<T>);
|
||||
if (atflCd) setCookie("atfl-cd-key", atflCd as string);
|
||||
|
||||
const files = e.target.files;
|
||||
if (!files) return;
|
||||
if (bbsInfo?.atflMaxCnt && files.length > bbsInfo.atflMaxCnt) {
|
||||
alert("첨부파일 최대 개수를 초과했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const fileSize = Math.round(file.size / 1024); // Byte -> KB
|
||||
if (bbsInfo?.atflMaxFileSz && fileSize > bbsInfo.atflMaxFileSz) {
|
||||
alert("첨부파일 최대 크기를 초과했습니다.");
|
||||
return;
|
||||
}
|
||||
handleTusUpload(file, {
|
||||
handleResponse: handleTusResponse,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const saveInterceptors = async (data: T) => {
|
||||
if (deleteAttachments.length > 0) {
|
||||
await Promise.all(
|
||||
@@ -105,6 +129,10 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo));
|
||||
}, [storedBbsInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
setCookie("atfl-cd-key", "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormWrapper form={form} handleSave={saveInterceptors}>
|
||||
<div className="card">
|
||||
@@ -428,54 +456,48 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell className="border">
|
||||
<FileUploader
|
||||
id="atflCd"
|
||||
multiple
|
||||
className="flex !h-auto max-w-full flex-row-reverse gap-4 [&>label]:h-[3.2rem]"
|
||||
onUpload={(e) => {
|
||||
const files = e.target.files;
|
||||
if (!files) return;
|
||||
for (const file of files) {
|
||||
handleTusUpload(file, {
|
||||
handleResponse: handleTusResponse,
|
||||
});
|
||||
}
|
||||
}}
|
||||
customPreview={
|
||||
<div className="flex w-full gap-4">
|
||||
{Object.entries(attachments).map(
|
||||
([atflId, attachment]) => {
|
||||
if (deleteAttachments.includes(atflId)) return null;
|
||||
return (
|
||||
<div
|
||||
key={atflId}
|
||||
className="flex h-[3.2rem] items-center gap-2 rounded-sm border border-border bg-background px-3 py-2 text-foreground"
|
||||
>
|
||||
<span>{attachment.orgnlFileNm}</span>
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex size-7 items-center justify-center rounded-full p-0"
|
||||
onClick={() =>
|
||||
setDeleteAttachments((prev) => [
|
||||
...prev,
|
||||
atflId,
|
||||
])
|
||||
}
|
||||
{bbsInfo?.atflUseYn === "Y" && (
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableCell className="border">
|
||||
<FileUploader
|
||||
id="atflCd"
|
||||
multiple
|
||||
className="flex !h-auto max-w-full flex-row-reverse gap-4 [&>label]:h-[3.2rem]"
|
||||
onUpload={handleUpload}
|
||||
customPreview={
|
||||
<div className="flex w-full gap-4">
|
||||
{Object.entries(attachments).map(
|
||||
([atflId, attachment]) => {
|
||||
if (deleteAttachments.includes(atflId)) return null;
|
||||
return (
|
||||
<div
|
||||
key={atflId}
|
||||
className="flex h-[3.2rem] items-center gap-2 rounded-sm border border-border bg-background px-3 py-2 text-foreground"
|
||||
>
|
||||
<i className="diveicon di-x text-xs" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<span>{attachment.orgnlFileNm}</span>
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex size-7 items-center justify-center rounded-full p-0"
|
||||
onClick={() =>
|
||||
setDeleteAttachments((prev) => [
|
||||
...prev,
|
||||
atflId,
|
||||
])
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-x text-xs" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
@@ -36,12 +36,12 @@ type BbsFormProps = {
|
||||
|
||||
function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
const bbsTypeCd = form.watch("bbsTypeCd");
|
||||
const formDisabled = !!bbsTypeCd && bbsTypeCd !== "BBSNORMAL";
|
||||
const disabledForm = !!bbsTypeCd && bbsTypeCd !== "BBSNORMAL";
|
||||
|
||||
const { data: boardTemplate } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.BBS_TEMPLATES, bbsTypeCd],
|
||||
queryFn: () => BoardTemplateApi.getBoardTemplate(bbsTypeCd),
|
||||
enabled: !formDisabled,
|
||||
enabled: disabledForm,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -128,7 +128,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.rfrncUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="rfrncUseY" />
|
||||
@@ -158,7 +158,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.atflUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="atflUseY" />
|
||||
@@ -199,7 +199,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
className="w-[12rem] !text-[1.3rem]"
|
||||
placeholder="최대 개수 입력"
|
||||
disabled={
|
||||
form.getValues("atflUseYn") === "N" || formDisabled
|
||||
form.getValues("atflUseYn") === "N" || disabledForm
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -234,7 +234,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
className="w-[12rem] !text-[1.3rem]"
|
||||
placeholder="최대 크기 입력"
|
||||
disabled={
|
||||
form.getValues("atflUseYn") === "N" || formDisabled
|
||||
form.getValues("atflUseYn") === "N" || disabledForm
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -256,7 +256,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.ansUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="ansUseY" />
|
||||
@@ -315,7 +315,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.cmntUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="cmntUseY" />
|
||||
@@ -345,7 +345,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.dgstfnEvlUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="dgstfnEvlUseY" />
|
||||
@@ -375,7 +375,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.popupUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="popupUseY" />
|
||||
@@ -405,7 +405,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.smsUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="smsUseY" />
|
||||
@@ -435,7 +435,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.upndFixUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="upndFixUseY" />
|
||||
@@ -465,7 +465,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
{...field}
|
||||
value={boardTemplate?.pstgPrdUseYn ?? field.value}
|
||||
onValueChange={field.onChange}
|
||||
disabled={formDisabled}
|
||||
disabled={disabledForm}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="pstgPrdUseY" />
|
||||
|
||||
Reference in New Issue
Block a user