feat: 이미지 및 동영상 게시글 작업(cs, features)
This commit is contained in:
@@ -52,7 +52,15 @@ export class AttachmentApiService implements AttachmentService {
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
public async getMovieThumbnailUrl(atflId: string): Promise<string> {
|
||||
public async getStreamVideoUrlByAtflId(atflId: string): Promise<string> {
|
||||
const response = await fetch(
|
||||
`${ENDPOINT.ATTACH_SERVICE.STREAM_VIDEO}/${atflId}`,
|
||||
);
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
public async getMovieThumbnailUrlByAtflId(atflId: string): Promise<string> {
|
||||
const response = await fetch(
|
||||
`${ENDPOINT.ATTACH_SERVICE.MOVIE_THUMBNAIL}/${atflId}`,
|
||||
);
|
||||
@@ -60,6 +68,14 @@ export class AttachmentApiService implements AttachmentService {
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
public async getMovieThumbnailUrlByAtflCd(atflCd: string): Promise<string> {
|
||||
const response = await fetch(
|
||||
`${ENDPOINT.ATTACH_SERVICE.MOVIE_THUMBNAIL}/atflCd/${atflCd}`,
|
||||
);
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
public async downloadAttachmentByAtflCd(
|
||||
atflCd: string,
|
||||
): Promise<AxiosResponse> {
|
||||
|
||||
@@ -40,6 +40,8 @@ export interface AttachmentService {
|
||||
deleteAttachmentByAtflCd: (atflCd: string) => Promise<void>;
|
||||
getImageUrlByAtflId: (atflId: string) => Promise<string>;
|
||||
getImageUrlByAtflCd: (atflCd: string) => Promise<string>;
|
||||
getMovieThumbnailUrl: (atflId: string) => Promise<string>;
|
||||
getStreamVideoUrlByAtflId: (atflId: string) => Promise<string>;
|
||||
getMovieThumbnailUrlByAtflId: (atflId: string) => Promise<string>;
|
||||
getMovieThumbnailUrlByAtflCd: (atflCd: string) => Promise<string>;
|
||||
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
|
||||
export class PostApiService implements PostService {
|
||||
public async getPostList(
|
||||
bbsId: string,
|
||||
bbsId: number,
|
||||
params: PostParams,
|
||||
): Promise<PostList> {
|
||||
const response = await client.get<PostList>(
|
||||
@@ -24,7 +24,7 @@ export class PostApiService implements PostService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo> {
|
||||
public async getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo> {
|
||||
const response = await client.get<PostInfo>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
|
||||
);
|
||||
@@ -41,8 +41,8 @@ export class PostApiService implements PostService {
|
||||
}
|
||||
|
||||
public async updatePost(
|
||||
bbsId: string,
|
||||
pstNo: string,
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
data: PostUpdateFormData,
|
||||
): Promise<Post> {
|
||||
const validatedData = postUpdateFormSchema.parse(data);
|
||||
@@ -54,8 +54,8 @@ export class PostApiService implements PostService {
|
||||
}
|
||||
|
||||
public async deletePost(
|
||||
bbsId: string,
|
||||
pstNo: string,
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
pstPswd?: string,
|
||||
): Promise<void> {
|
||||
await client.delete(
|
||||
|
||||
@@ -8,13 +8,15 @@ export interface PostParams extends PageableParams {
|
||||
upndFixYn?: string;
|
||||
prdPstgYn?: string;
|
||||
siteId?: string;
|
||||
bbsTypeCd?: string;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
bbsId: number;
|
||||
rowNo: number;
|
||||
bbsId: number;
|
||||
pstNo: number;
|
||||
pstTtl: string;
|
||||
pstCn: string;
|
||||
inqNocs: number;
|
||||
rfrncUrl: string;
|
||||
popupYn: string;
|
||||
@@ -73,13 +75,13 @@ export type PostFormData = z.infer<typeof postFormSchema>;
|
||||
export type PostUpdateFormData = z.infer<typeof postUpdateFormSchema>;
|
||||
|
||||
export interface PostService {
|
||||
getPostList(bbsId: string, params: PostParams): Promise<PostList>;
|
||||
getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo>;
|
||||
getPostList(bbsId: number, params: PostParams): Promise<PostList>;
|
||||
getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo>;
|
||||
createPost(data: PostFormData): Promise<Post>;
|
||||
updatePost(
|
||||
bbsId: string,
|
||||
pstNo: string,
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
data: PostUpdateFormData,
|
||||
): Promise<Post>;
|
||||
deletePost(bbsId: string, pstNo: string, pstPswd?: string): Promise<void>;
|
||||
deletePost(bbsId: number, pstNo: number, pstPswd?: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -20,12 +20,12 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -20,12 +20,12 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ export const ENDPOINT = {
|
||||
MOVIE_THUMBNAIL: "/attach-service/api/v1/movie/thumbnail",
|
||||
ATTACHMENTS: "/attach-service/api/v1/atfl/attachments",
|
||||
DOWNLOAD: "/attach-service/api/v1/atflcd/download",
|
||||
STREAM_VIDEO: "/attach-service/api/v1/stream/video",
|
||||
},
|
||||
USER_SERVICE: {
|
||||
LOGIN: "/user-service/login",
|
||||
|
||||
@@ -7,18 +7,18 @@ export const usePrompt = () => {
|
||||
|
||||
const prompt = (message: string) => {
|
||||
setMessage(message);
|
||||
return new Promise<string>((resolve) => {
|
||||
return new Promise<{ resolved: boolean; value?: string }>((resolve) => {
|
||||
setResolve(() => resolve);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (value: string) => {
|
||||
resolve?.(value);
|
||||
resolve?.({ resolved: true, value });
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
resolve?.("");
|
||||
resolve?.({ resolved: false });
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,15 @@ import { createContext, useState } from "react";
|
||||
|
||||
interface PromptContext {
|
||||
message?: string;
|
||||
resolve?: (value: string) => void;
|
||||
resolve?: ({
|
||||
resolved,
|
||||
value,
|
||||
}: {
|
||||
resolved: boolean;
|
||||
value?: string;
|
||||
}) => void;
|
||||
setMessage: (message: string) => void;
|
||||
setResolve: (resolve: (value: string) => void) => void;
|
||||
setResolve: (resolve: () => void) => void;
|
||||
}
|
||||
|
||||
export const PromptContext = createContext<PromptContext>({
|
||||
@@ -16,7 +22,10 @@ export const PromptContext = createContext<PromptContext>({
|
||||
|
||||
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [message, setMessage] = useState<string>();
|
||||
const [resolve, setResolve] = useState<(value: string) => void>();
|
||||
const [resolve, setResolve] =
|
||||
useState<
|
||||
({ resolved, value }: { resolved: boolean; value?: string }) => void
|
||||
>();
|
||||
|
||||
return (
|
||||
<PromptContext.Provider
|
||||
|
||||
@@ -56,7 +56,7 @@ function GridViewDataTable<T>({
|
||||
key={row.id}
|
||||
onClick={() => onRowClick?.(row.original)}
|
||||
className={cn(
|
||||
"rounded-md border border-border bg-background p-4 shadow-md",
|
||||
"overflow-hidden rounded-md border border-border bg-background p-4 shadow-md",
|
||||
onRowClick && "cursor-pointer hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import dayjs from "dayjs";
|
||||
import Image from "next/image";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Badge, Checkbox, Skeleton } from "@common/components";
|
||||
import { Badge, Checkbox, Skeleton, cn } from "@common/components";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import type { Post } from "@/entities/post";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import type { BbsType } from "@/shared/store/menu-store";
|
||||
|
||||
declare module "@tanstack/react-table" {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface TableMeta<TData> {
|
||||
bbsTypeCd?: BbsType;
|
||||
}
|
||||
}
|
||||
|
||||
export const createColumns = (
|
||||
thumbRefs: React.MutableRefObject<Map<number, string>>,
|
||||
@@ -22,68 +30,120 @@ export const createColumns = (
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const { pstTtl, inqNocs } = row.original;
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
cell: ({ row, table }) => {
|
||||
const { atflCd, pstNo, pstTtl, inqNocs, rgtrNm, regDt } = row.original;
|
||||
const bbsTypeCd = table.options.meta?.bbsTypeCd;
|
||||
|
||||
if (bbsTypeCd === "BBSIMAGE") {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
|
||||
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
|
||||
<Badge color="blue">NEW</Badge>
|
||||
)}
|
||||
<span className="text-[1.6rem] font-bold text-label">
|
||||
{pstTtl}
|
||||
</span>
|
||||
</div>
|
||||
<ThumbImage
|
||||
atflCd={atflCd}
|
||||
pstNo={pstNo}
|
||||
thumbRefs={thumbRefs}
|
||||
bbsTypeCd={bbsTypeCd}
|
||||
/>
|
||||
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
|
||||
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
|
||||
<Badge color="blue">NEW</Badge>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{rgtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-3xl font-bold leading-none text-label">
|
||||
{pstTtl}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (bbsTypeCd === "BBSMOVIE") {
|
||||
return (
|
||||
<div className="-m-4">
|
||||
<ThumbImage
|
||||
atflCd={atflCd}
|
||||
pstNo={pstNo}
|
||||
thumbRefs={thumbRefs}
|
||||
bbsTypeCd={bbsTypeCd}
|
||||
classes={{
|
||||
image: "object-cover",
|
||||
}}
|
||||
/>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
|
||||
{dayjs().diff(dayjs(regDt), "hour") < 24 && (
|
||||
<Badge color="blue">NEW</Badge>
|
||||
)}
|
||||
<span className="text-[1.6rem] font-bold text-label">
|
||||
{pstTtl}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{rgtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "atflCd",
|
||||
cell: ({ row }) => {
|
||||
const { atflCd, pstNo } = row.original;
|
||||
return <ThumbImage atflCd={atflCd} pstNo={pstNo} thumbRefs={thumbRefs} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rgtrNm",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{row.original.rgtrNm}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "regDt",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(row.original.regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
type ThumbImageProps = {
|
||||
atflCd: string;
|
||||
pstNo: number;
|
||||
thumbRefs: React.MutableRefObject<Map<number, string>>;
|
||||
bbsTypeCd?: string;
|
||||
classes?: {
|
||||
image?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
|
||||
const ThumbImage = ({
|
||||
atflCd,
|
||||
pstNo,
|
||||
thumbRefs,
|
||||
bbsTypeCd,
|
||||
classes,
|
||||
}: ThumbImageProps) => {
|
||||
const prevImage = thumbRefs.current.get(pstNo);
|
||||
|
||||
const { data: imageUrl, isLoading } = useQuery({
|
||||
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
|
||||
queryFn: () => AttachmentApi.getImageUrlByAtflCd(atflCd),
|
||||
queryFn: () => {
|
||||
if (bbsTypeCd === "BBSIMAGE") {
|
||||
return AttachmentApi.getImageUrlByAtflCd(atflCd);
|
||||
} else if (bbsTypeCd === "BBSMOVIE") {
|
||||
return AttachmentApi.getMovieThumbnailUrlByAtflCd(atflCd);
|
||||
}
|
||||
},
|
||||
select: (url) => {
|
||||
if (url) thumbRefs.current.set(pstNo, url);
|
||||
return url;
|
||||
@@ -93,17 +153,17 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div className="my-6 w-full">
|
||||
<Skeleton className="mx-auto h-[16rem] w-full" />
|
||||
<div className="h-[16rem] w-full">
|
||||
<Skeleton className="h-full w-full" />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (imageUrl)
|
||||
return (
|
||||
<div className="relative my-6 flex h-[16rem] w-full items-center justify-center">
|
||||
<div className="relative flex h-[16rem] w-full items-center justify-center">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
className="object-contain"
|
||||
className={cn("object-contain", classes?.image)}
|
||||
fill
|
||||
alt="썸네일 이미지"
|
||||
/>
|
||||
@@ -111,7 +171,7 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="my-6 flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
|
||||
<div className="flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
|
||||
<i className="diveicon di-image text-3xl" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
{children}
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
<Button
|
||||
@@ -68,7 +68,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,8 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
|
||||
if (userInfo?.userUnqId) {
|
||||
cmntPswd = userInfo?.userUnqId;
|
||||
} else {
|
||||
cmntPswd = await prompt("비밀번호를 입력해주세요.");
|
||||
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
|
||||
if (resolved) cmntPswd = value ?? "";
|
||||
}
|
||||
return cmntPswd;
|
||||
};
|
||||
|
||||
@@ -24,11 +24,10 @@ import {
|
||||
} from "@common/components";
|
||||
import { PostApi, type Post, type PostParams } from "@/entities/post";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { BbsType } from "@/shared/store/menu-store";
|
||||
import { useMenuStore, useUserStore } from "@/shared/lib";
|
||||
import { GridViewDataTable } from "@/shared/ui/grid-view-data-table";
|
||||
import { columns } from "../model/post-table-columns";
|
||||
import { createColumns } from "../model/grid-view-table-columns";
|
||||
import { columns as tableColumns } from "../model/post-table-columns";
|
||||
import { createColumns as createGridViewColumns } from "../model/grid-view-table-columns";
|
||||
|
||||
function PostPage({ bbsId }: { bbsId: string }) {
|
||||
const router = useRouter();
|
||||
@@ -36,7 +35,6 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
const { confirm } = useConfirm();
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state);
|
||||
const bbsType = bbsInfo?.bbsTypeCd as BbsType;
|
||||
|
||||
const [postParams, setPostParams] = useState<PostParams>({
|
||||
keywordType: "title",
|
||||
@@ -53,7 +51,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
const { data: upndFixPostList } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
|
||||
queryFn: () =>
|
||||
PostApi.getPostList(bbsId, {
|
||||
PostApi.getPostList(parseInt(bbsId), {
|
||||
...postParams,
|
||||
upndFixYn: bbsInfo?.upndFixUseYn,
|
||||
}),
|
||||
@@ -77,7 +75,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
} = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId],
|
||||
queryFn: () =>
|
||||
PostApi.getPostList(bbsId, {
|
||||
PostApi.getPostList(parseInt(bbsId), {
|
||||
...postParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
@@ -85,9 +83,14 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
const isGridViewPage =
|
||||
bbsInfo?.bbsTypeCd === "BBSIMAGE" || bbsInfo?.bbsTypeCd === "BBSMOVIE";
|
||||
|
||||
const table = useReactTable({
|
||||
data: [...(upndFixPostList?.content ?? []), ...(postList?.content ?? [])],
|
||||
columns,
|
||||
data: isGridViewPage
|
||||
? (postList?.content ?? [])
|
||||
: [...(upndFixPostList?.content ?? []), ...(postList?.content ?? [])],
|
||||
columns: isGridViewPage ? createGridViewColumns(thumbRefs) : tableColumns,
|
||||
state: { pagination, rowSelection },
|
||||
rowCount: postList?.totalElements ?? 0,
|
||||
manualPagination: true,
|
||||
@@ -95,19 +98,10 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
meta: { bbsTypeCd: bbsInfo?.bbsTypeCd },
|
||||
});
|
||||
|
||||
const gridViewTable = useReactTable({
|
||||
data: postList?.content ?? [],
|
||||
columns: createColumns(thumbRefs),
|
||||
state: { pagination, rowSelection },
|
||||
rowCount: postList?.totalElements ?? 0,
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
});
|
||||
const DataTableComponent = isGridViewPage ? GridViewDataTable : DataTable;
|
||||
|
||||
const onPostDelete = async () => {
|
||||
const selectedPosts = table
|
||||
@@ -118,7 +112,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
setRowSelection({});
|
||||
await Promise.all(
|
||||
selectedPosts.map((pstNo) =>
|
||||
PostApi.deletePost(bbsId, pstNo.toString(), userInfo?.userUnqId),
|
||||
PostApi.deletePost(parseInt(bbsId), pstNo, userInfo?.userUnqId),
|
||||
),
|
||||
);
|
||||
refetch();
|
||||
@@ -211,19 +205,11 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||
{isFetched ? (
|
||||
bbsType === "BBSIMAGE" ? (
|
||||
<GridViewDataTable
|
||||
table={gridViewTable}
|
||||
onRowClick={handleRowClick}
|
||||
viewPagination
|
||||
/>
|
||||
) : (
|
||||
<DataTable
|
||||
table={table}
|
||||
onRowClick={handleRowClick}
|
||||
viewPagination
|
||||
/>
|
||||
)
|
||||
<DataTableComponent
|
||||
table={table}
|
||||
onRowClick={handleRowClick}
|
||||
viewPagination
|
||||
/>
|
||||
) : (
|
||||
<Skeleton className="h-[36rem] w-full" />
|
||||
)}
|
||||
|
||||
@@ -29,7 +29,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
|
||||
const { data: postData } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { data: downloadResponse, isFetching } = useQuery({
|
||||
@@ -47,7 +47,8 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
});
|
||||
|
||||
const { mutate: deletePost } = useMutation({
|
||||
mutationFn: (pstPswd: string) => PostApi.deletePost(bbsId, pstNo, pstPswd),
|
||||
mutationFn: (pstPswd: string) =>
|
||||
PostApi.deletePost(parseInt(bbsId), parseInt(pstNo), pstPswd),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.", router.back);
|
||||
},
|
||||
@@ -58,7 +59,8 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
if (userInfo?.userUnqId) {
|
||||
pstPswd = userInfo?.userUnqId;
|
||||
} else {
|
||||
pstPswd = await prompt("비밀번호를 입력해주세요.");
|
||||
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
|
||||
if (resolved) pstPswd = value ?? "";
|
||||
}
|
||||
deletePost(pstPswd);
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ function WithPreviewFileUploader<T extends PostFormData | PostUpdateFormData>({
|
||||
imageUrl = await AttachmentApi.getImageUrlByAtflId(atflId);
|
||||
}
|
||||
if (bbsInfo?.bbsTypeCd === "BBSMOVIE") {
|
||||
imageUrl = await AttachmentApi.getMovieThumbnailUrl(atflId);
|
||||
imageUrl = await AttachmentApi.getMovieThumbnailUrlByAtflId(atflId);
|
||||
}
|
||||
setThumbnailUrl(imageUrl);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
|
||||
import { AuthWrapper } from "@/entities/auth";
|
||||
import { MenuWrapper } from "@/entities/menu";
|
||||
import { StoreProvider } from "@/shared/providers";
|
||||
import { PromptProvider, StoreProvider } from "@/shared/providers";
|
||||
import { AlertDialog } from "@/shared/ui/alert-dialog";
|
||||
import { PromptDialog } from "@/shared/ui/prompt-dialog";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { initializeNextClientConfig } from "@common/config";
|
||||
|
||||
@@ -19,17 +20,23 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<StoreProvider>
|
||||
<ConfirmProvider>
|
||||
<AuthWrapper>
|
||||
<MenuWrapper>
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
{children}
|
||||
</DndProvider>
|
||||
</MenuWrapper>
|
||||
<AlertDialog />
|
||||
<ConfirmDialog />
|
||||
</AuthWrapper>
|
||||
</ConfirmProvider>
|
||||
<PromptProvider>
|
||||
<ConfirmProvider>
|
||||
<AuthWrapper>
|
||||
<MenuWrapper>
|
||||
<DndProvider
|
||||
backend={MultiBackend}
|
||||
options={getBackendOptions()}
|
||||
>
|
||||
{children}
|
||||
</DndProvider>
|
||||
</MenuWrapper>
|
||||
<AlertDialog />
|
||||
<PromptDialog />
|
||||
<ConfirmDialog />
|
||||
</AuthWrapper>
|
||||
</ConfirmProvider>
|
||||
</PromptProvider>
|
||||
</StoreProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
@@ -14,19 +14,19 @@ import {
|
||||
|
||||
export class PostApiService implements PostService {
|
||||
public async getPostList(
|
||||
bbsId: string,
|
||||
bbsId: number,
|
||||
params: PostParams,
|
||||
): Promise<PostList> {
|
||||
const response = await client.get<PostList>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}`,
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo> {
|
||||
public async getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo> {
|
||||
const response = await client.get<PostInfo>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -34,34 +34,33 @@ export class PostApiService implements PostService {
|
||||
public async createPost(data: PostFormData): Promise<Post> {
|
||||
const validatedData = postFormSchema.parse(data);
|
||||
const response = await client.post<Post>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}`,
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updatePost(
|
||||
bbsId: string,
|
||||
pstNo: string,
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
data: PostUpdateFormData,
|
||||
): Promise<Post> {
|
||||
const validatedData = postUpdateFormSchema.parse(data);
|
||||
const response = await client.put<Post>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deletePost(
|
||||
bbsId: string,
|
||||
pstNo: string,
|
||||
pstPswd?: string,
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
delRsn?: string,
|
||||
): Promise<void> {
|
||||
await client.delete(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_USER}/${bbsId}/${pstNo}`,
|
||||
{ params: { pstPswd } },
|
||||
);
|
||||
await client.delete(`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`, {
|
||||
params: { delRsn },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,15 @@ export interface PostParams extends PageableParams {
|
||||
upndFixYn?: string;
|
||||
prdPstgYn?: string;
|
||||
siteId?: string;
|
||||
bbsTypeCd?: string;
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
bbsId: number;
|
||||
rowNo: number;
|
||||
bbsId: number;
|
||||
pstNo: number;
|
||||
pstTtl: string;
|
||||
pstCn: string;
|
||||
inqNocs: number;
|
||||
rfrncUrl: string;
|
||||
popupYn: string;
|
||||
@@ -73,13 +75,13 @@ export type PostFormData = z.infer<typeof postFormSchema>;
|
||||
export type PostUpdateFormData = z.infer<typeof postUpdateFormSchema>;
|
||||
|
||||
export interface PostService {
|
||||
getPostList(bbsId: string, params: PostParams): Promise<PostList>;
|
||||
getPostInfo(bbsId: string, pstNo: string): Promise<PostInfo>;
|
||||
getPostList(bbsId: number, params: PostParams): Promise<PostList>;
|
||||
getPostInfo(bbsId: number, pstNo: number): Promise<PostInfo>;
|
||||
createPost(data: PostFormData): Promise<Post>;
|
||||
updatePost(
|
||||
bbsId: string,
|
||||
pstNo: string,
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
data: PostUpdateFormData,
|
||||
): Promise<Post>;
|
||||
deletePost(bbsId: string, pstNo: string, pstPswd?: string): Promise<void>;
|
||||
deletePost(bbsId: number, pstNo: number, delRsn?: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -19,13 +19,13 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
|
||||
const { data: postInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { mutateAsync: updatePost } = useMutation({
|
||||
mutationFn: (data: PostUpdateFormData) =>
|
||||
PostApi.updatePost(bbsId, pstNo, data),
|
||||
PostApi.updatePost(parseInt(bbsId), parseInt(pstNo), data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ENDPOINT = {
|
||||
BOARD_SERVICE: {
|
||||
BBS: "/board-service/api/v1/bbs",
|
||||
BBS_TEMPLATES: "/board-service/api/v1/bbsTmplt",
|
||||
POSTS_USER: "/board-service/api/v1/posts/user",
|
||||
POSTS: "/board-service/api/v1/posts",
|
||||
POSTS_COMMENTS: "/board-service/api/v1/posts-cmnts",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,18 +7,18 @@ export const usePrompt = () => {
|
||||
|
||||
const prompt = (message: string) => {
|
||||
setMessage(message);
|
||||
return new Promise<string>((resolve) => {
|
||||
return new Promise<{ resolved: boolean; value?: string }>((resolve) => {
|
||||
setResolve(() => resolve);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (value: string) => {
|
||||
resolve?.(value);
|
||||
resolve?.({ resolved: true, value });
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
resolve?.("");
|
||||
resolve?.({ resolved: false });
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { StoreProvider } from "./store-provider";
|
||||
export { PromptProvider } from "./prompt-provider";
|
||||
|
||||
@@ -2,9 +2,15 @@ import { createContext, useState } from "react";
|
||||
|
||||
interface PromptContext {
|
||||
message?: string;
|
||||
resolve?: (value: string) => void;
|
||||
resolve?: ({
|
||||
resolved,
|
||||
value,
|
||||
}: {
|
||||
resolved: boolean;
|
||||
value?: string;
|
||||
}) => void;
|
||||
setMessage: (message: string) => void;
|
||||
setResolve: (resolve: (value: string) => void) => void;
|
||||
setResolve: (resolve: () => void) => void;
|
||||
}
|
||||
|
||||
export const PromptContext = createContext<PromptContext>({
|
||||
@@ -16,7 +22,10 @@ export const PromptContext = createContext<PromptContext>({
|
||||
|
||||
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [message, setMessage] = useState<string>();
|
||||
const [resolve, setResolve] = useState<(value: string) => void>();
|
||||
const [resolve, setResolve] =
|
||||
useState<
|
||||
({ resolved, value }: { resolved: boolean; value?: string }) => void
|
||||
>();
|
||||
|
||||
return (
|
||||
<PromptContext.Provider
|
||||
|
||||
@@ -56,7 +56,7 @@ function GridViewDataTable<T>({
|
||||
key={row.id}
|
||||
onClick={() => onRowClick?.(row.original)}
|
||||
className={cn(
|
||||
"rounded-md border border-border bg-background p-4 shadow-md",
|
||||
"overflow-hidden rounded-md border border-border bg-background p-4 shadow-md",
|
||||
onRowClick && "cursor-pointer hover:bg-accent",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Input,
|
||||
} from "@common/components";
|
||||
import { usePrompt } from "@/shared/lib/use-prompt";
|
||||
|
||||
function PromptDialog() {
|
||||
const { message, isOpen, onSubmit, onCancel } = usePrompt();
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onCancel}>
|
||||
<DialogContent aria-describedby={undefined}>
|
||||
<div className="flex flex-col gap-6 pb-4 pt-8">
|
||||
<p className="whitespace-pre-line text-center text-[1.4rem] text-foreground">
|
||||
{message}
|
||||
</p>
|
||||
<Input
|
||||
type="text"
|
||||
value={value}
|
||||
className="!text-[1.3rem]"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button onClick={() => onSubmit(value)}>확인</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button onClick={onCancel} color="white">
|
||||
취소
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { PromptDialog };
|
||||
@@ -1,12 +1,19 @@
|
||||
import dayjs from "dayjs";
|
||||
import Image from "next/image";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Badge, Checkbox, Skeleton } from "@common/components";
|
||||
import { Badge, Checkbox, Skeleton, cn } from "@common/components";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import type { Post } from "@/entities/post";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useMenuStore } from "@/shared/lib";
|
||||
import type { BbsType } from "@/shared/store/menu-store";
|
||||
|
||||
declare module "@tanstack/react-table" {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface TableMeta<TData> {
|
||||
bbsTypeCd?: BbsType;
|
||||
}
|
||||
}
|
||||
|
||||
export const createColumns = (
|
||||
thumbRefs: React.MutableRefObject<Map<number, string>>,
|
||||
@@ -23,73 +30,117 @@ export const createColumns = (
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const { pstTtl, inqNocs } = row.original;
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
cell: ({ row, table }) => {
|
||||
const { atflCd, pstNo, pstTtl, inqNocs, rgtrNm, regDt } = row.original;
|
||||
const bbsTypeCd = table.options.meta?.bbsTypeCd;
|
||||
|
||||
if (bbsTypeCd === "BBSIMAGE") {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
|
||||
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
|
||||
<Badge color="blue">NEW</Badge>
|
||||
)}
|
||||
<span className="text-[1.6rem] font-bold text-label">
|
||||
{pstTtl}
|
||||
</span>
|
||||
</div>
|
||||
<ThumbImage
|
||||
atflCd={atflCd}
|
||||
pstNo={pstNo}
|
||||
thumbRefs={thumbRefs}
|
||||
bbsTypeCd={bbsTypeCd}
|
||||
/>
|
||||
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
|
||||
{dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && (
|
||||
<Badge color="blue">NEW</Badge>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{rgtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-3xl font-bold leading-none text-label">
|
||||
{pstTtl}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (bbsTypeCd === "BBSMOVIE") {
|
||||
return (
|
||||
<div className="-m-4">
|
||||
<ThumbImage
|
||||
atflCd={atflCd}
|
||||
pstNo={pstNo}
|
||||
thumbRefs={thumbRefs}
|
||||
bbsTypeCd={bbsTypeCd}
|
||||
classes={{
|
||||
image: "object-cover",
|
||||
}}
|
||||
/>
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
{inqNocs >= 100 && <Badge color="red">HOT</Badge>}
|
||||
{dayjs().diff(dayjs(regDt), "hour") < 24 && (
|
||||
<Badge color="blue">NEW</Badge>
|
||||
)}
|
||||
<span className="text-[1.6rem] font-bold text-label">
|
||||
{pstTtl}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{rgtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "atflCd",
|
||||
cell: ({ row }) => {
|
||||
const { atflCd, pstNo } = row.original;
|
||||
return <ThumbImage atflCd={atflCd} pstNo={pstNo} thumbRefs={thumbRefs} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rgtrNm",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{row.original.rgtrNm}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "regDt",
|
||||
cell: ({ row }) => (
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(row.original.regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
type ThumbImageProps = {
|
||||
atflCd: string;
|
||||
pstNo: number;
|
||||
thumbRefs: React.MutableRefObject<Map<number, string>>;
|
||||
bbsTypeCd?: string;
|
||||
classes?: {
|
||||
image?: string;
|
||||
};
|
||||
};
|
||||
|
||||
const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
|
||||
const ThumbImage = ({
|
||||
atflCd,
|
||||
pstNo,
|
||||
thumbRefs,
|
||||
bbsTypeCd,
|
||||
classes,
|
||||
}: ThumbImageProps) => {
|
||||
const prevImage = thumbRefs.current.get(pstNo);
|
||||
|
||||
const { bbsInfo } = useMenuStore((state) => state);
|
||||
|
||||
const { data: imageUrl, isLoading } = useQuery({
|
||||
queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, atflCd],
|
||||
queryFn: () => {
|
||||
if (bbsInfo?.bbsTypeCd === "BBSIMAGE") {
|
||||
if (bbsTypeCd === "BBSIMAGE") {
|
||||
return AttachmentApi.getImageUrlByAtflCd(atflCd);
|
||||
} else if (bbsInfo?.bbsTypeCd === "BBSMOVIE") {
|
||||
} else if (bbsTypeCd === "BBSMOVIE") {
|
||||
return AttachmentApi.getMovieThumbnailUrlByAtflCd(atflCd);
|
||||
}
|
||||
},
|
||||
@@ -102,17 +153,17 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<div className="my-6 w-full">
|
||||
<Skeleton className="mx-auto h-[16rem] w-full" />
|
||||
<div className="h-[16rem] w-full">
|
||||
<Skeleton className="h-full w-full" />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (imageUrl)
|
||||
return (
|
||||
<div className="relative my-6 flex h-[16rem] w-full items-center justify-center">
|
||||
<div className="relative flex h-[16rem] w-full items-center justify-center">
|
||||
<Image
|
||||
src={imageUrl}
|
||||
className="object-contain"
|
||||
className={cn("object-contain", classes?.image)}
|
||||
fill
|
||||
alt="썸네일 이미지"
|
||||
/>
|
||||
@@ -120,7 +171,7 @@ const ThumbImage = ({ atflCd, pstNo, thumbRefs }: ThumbImageProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="my-6 flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
|
||||
<div className="flex h-[16rem] w-full items-center justify-center rounded-sm border border-border bg-background">
|
||||
<i className="diveicon di-image text-3xl" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export const columns: ColumnDef<Post & { isUpndFixPost?: boolean }>[] = [
|
||||
return (
|
||||
<Badge
|
||||
color="orange"
|
||||
className="flex size-10 items-center justify-center"
|
||||
className="flex size-8 items-center justify-center"
|
||||
>
|
||||
<i className="diveicon di-volume-up-fill" />
|
||||
</Badge>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { Button } from "@common/components";
|
||||
import { useMenuStore, useUserStore } from "@/shared/lib";
|
||||
import { useMenuStore, usePrompt, useUserStore } from "@/shared/lib";
|
||||
|
||||
type PostActionProps = {
|
||||
bbsId: string;
|
||||
pstNo: string;
|
||||
children: React.ReactNode;
|
||||
handleDelete: () => Promise<void>;
|
||||
handleDelete: (delRsn?: string) => Promise<void>;
|
||||
};
|
||||
|
||||
function PostAction({ children, handleDelete }: PostActionProps) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { confirm } = useConfirm();
|
||||
const { prompt } = usePrompt();
|
||||
const { bbsInfo } = useMenuStore((state) => state);
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
|
||||
const onDelete = async () => {
|
||||
if (await confirm("삭제하시겠습니까?")) handleDelete();
|
||||
const { resolved, value } = await prompt("삭제 사유를 입력해주세요.");
|
||||
if (resolved) handleDelete(value);
|
||||
};
|
||||
|
||||
const checkAnsUseAuthrt = () => {
|
||||
@@ -35,7 +35,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
{children}
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
<Button
|
||||
@@ -68,7 +68,7 @@ function PostAction({ children, handleDelete }: PostActionProps) {
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,8 @@ function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
|
||||
if (userInfo?.userUnqId) {
|
||||
cmntPswd = userInfo?.userUnqId;
|
||||
} else {
|
||||
cmntPswd = await prompt("비밀번호를 입력해주세요.");
|
||||
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
|
||||
if (resolved) cmntPswd = value ?? "";
|
||||
}
|
||||
return cmntPswd;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
@@ -24,7 +23,7 @@ import {
|
||||
} from "@common/components";
|
||||
import { PostApi, type Post, type PostParams } from "@/entities/post";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useMenuStore, useUserStore } from "@/shared/lib";
|
||||
import { useMenuStore, usePrompt } from "@/shared/lib";
|
||||
import { GridViewDataTable } from "@/shared/ui/grid-view-data-table";
|
||||
import { columns as tableColumns } from "../model/post-table-columns";
|
||||
import { createColumns as createGridViewColumns } from "../model/grid-view-table-columns";
|
||||
@@ -32,8 +31,7 @@ import { createColumns as createGridViewColumns } from "../model/grid-view-table
|
||||
function PostPage({ bbsId }: { bbsId: string }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { confirm } = useConfirm();
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { prompt } = usePrompt();
|
||||
const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state);
|
||||
const thumbRefs = useRef<Map<number, string>>(new Map());
|
||||
|
||||
@@ -48,9 +46,9 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const { data: upndFixPostList } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, bbsInfo],
|
||||
queryFn: () =>
|
||||
PostApi.getPostList(bbsId, {
|
||||
PostApi.getPostList(parseInt(bbsId), {
|
||||
...postParams,
|
||||
upndFixYn: bbsInfo?.upndFixUseYn,
|
||||
}),
|
||||
@@ -69,9 +67,9 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
isFetched,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId],
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId],
|
||||
queryFn: () =>
|
||||
PostApi.getPostList(bbsId, {
|
||||
PostApi.getPostList(parseInt(bbsId), {
|
||||
...postParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
@@ -94,6 +92,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
meta: { bbsTypeCd: bbsInfo?.bbsTypeCd },
|
||||
});
|
||||
|
||||
const DataTableComponent = isGridViewPage ? GridViewDataTable : DataTable;
|
||||
@@ -103,15 +102,16 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
.getFilteredSelectedRowModel()
|
||||
.rows.map((row) => row.original.pstNo);
|
||||
|
||||
if (await confirm("삭제하시겠습니까?")) {
|
||||
setRowSelection({});
|
||||
await Promise.all(
|
||||
selectedPosts.map((pstNo) =>
|
||||
PostApi.deletePost(bbsId, pstNo.toString(), userInfo?.userUnqId),
|
||||
),
|
||||
);
|
||||
refetch();
|
||||
}
|
||||
const { resolved, value: delRsn } =
|
||||
await prompt("삭제 사유를 입력해주세요.");
|
||||
if (!resolved) return;
|
||||
setRowSelection({});
|
||||
await Promise.all(
|
||||
selectedPosts.map((pstNo) =>
|
||||
PostApi.deletePost(parseInt(bbsId), pstNo, delRsn),
|
||||
),
|
||||
);
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
|
||||
@@ -8,28 +8,21 @@ import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { Button, Skeleton } from "@common/components";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import { PostApi } from "@/entities/post";
|
||||
import {
|
||||
useAlertStore,
|
||||
useUserStore,
|
||||
useMenuStore,
|
||||
usePrompt,
|
||||
} from "@/shared/lib";
|
||||
import { useAlertStore, useMenuStore } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { PostAction } from "./post-action";
|
||||
import { PostComment } from "./post-comment";
|
||||
|
||||
function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
const router = useRouter();
|
||||
const { prompt } = usePrompt();
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { bbsInfo } = useMenuStore((state) => state);
|
||||
|
||||
const [filename, setFilename] = useState("");
|
||||
|
||||
const { data: postData } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(bbsId, pstNo),
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, bbsId, pstNo],
|
||||
queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)),
|
||||
});
|
||||
|
||||
const { data: downloadResponse, isFetching } = useQuery({
|
||||
@@ -65,23 +58,14 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
enabled: !!videoAttachment?.atflId,
|
||||
});
|
||||
|
||||
const { mutate: deletePost } = useMutation({
|
||||
mutationFn: (pstPswd: string) => PostApi.deletePost(bbsId, pstNo, pstPswd),
|
||||
const { mutateAsync: deletePost } = useMutation({
|
||||
mutationFn: (delRsn?: string) =>
|
||||
PostApi.deletePost(parseInt(bbsId), parseInt(pstNo), delRsn),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
const handleDelete = async () => {
|
||||
let pstPswd = "";
|
||||
if (userInfo?.userUnqId) {
|
||||
pstPswd = userInfo?.userUnqId;
|
||||
} else {
|
||||
pstPswd = await prompt("비밀번호를 입력해주세요.");
|
||||
}
|
||||
deletePost(pstPswd);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!downloadResponse) return;
|
||||
try {
|
||||
@@ -123,7 +107,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PostAction bbsId={bbsId} pstNo={pstNo} handleDelete={handleDelete}>
|
||||
<PostAction bbsId={bbsId} pstNo={pstNo} handleDelete={deletePost}>
|
||||
<div className="card card-border !p-8">
|
||||
<h3 className="text-[2rem] font-bold text-foreground">
|
||||
{postData?.pstTtl}
|
||||
|
||||
Reference in New Issue
Block a user