From df9cb0a10cf67be66e8fdce998c3c170be62ea99 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 22 Dec 2025 10:43:14 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20build=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20RealGrid=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20key=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/popup/[popupSn]/page.tsx | 10 - .../view/survey-article/[srvySn]/page.tsx | 10 - .../view/survey-result/[srvySn]/page.tsx | 10 - apps/st/src/components/module/DataGrid.tsx | 2 +- apps/st/src/components/module/SearchForm.tsx | 4 +- .../statistics/model/statistics-interface.ts | 2 - apps/st/src/shared/lib/match-query-status.ts | 29 +- apps/st/src/widgets/dnd-tree/index.ts | 2 - .../src/widgets/dnd-tree/ui/custom-node.tsx | 105 ---- apps/st/src/widgets/dnd-tree/ui/dnd-tree.tsx | 138 ------ .../src/widgets/dnd-tree/ui/editable-node.tsx | 44 -- apps/st/src/widgets/form-wrapper/index.ts | 1 - .../widgets/form-wrapper/ui/form-wrapper.tsx | 63 --- .../post/api/post-evaluation-service.ts | 14 - apps/st/src/widgets/post/index.ts | 5 - .../post/model/grid-view-table-columns.tsx | 184 ------- .../post/model/post-evaluation-interface.ts | 10 - .../widgets/post/model/post-table-columns.tsx | 146 ------ .../widgets/post/ui/general-file-uploader.tsx | 121 ----- apps/st/src/widgets/post/ui/post-action.tsx | 106 ----- apps/st/src/widgets/post/ui/post-comment.tsx | 323 ------------- apps/st/src/widgets/post/ui/post-form.tsx | 449 ------------------ apps/st/src/widgets/post/ui/post-page.tsx | 239 ---------- apps/st/src/widgets/post/ui/post.tsx | 263 ---------- .../post/ui/posting-period-selector.tsx | 84 ---- apps/st/src/widgets/post/ui/sort-select.tsx | 32 -- .../post/ui/with-preview-file-uploader.tsx | 125 ----- 27 files changed, 18 insertions(+), 2503 deletions(-) delete mode 100644 apps/st/app/(without-base-layout)/view/popup/[popupSn]/page.tsx delete mode 100644 apps/st/app/(without-base-layout)/view/survey-article/[srvySn]/page.tsx delete mode 100644 apps/st/app/(without-base-layout)/view/survey-result/[srvySn]/page.tsx delete mode 100644 apps/st/src/widgets/dnd-tree/index.ts delete mode 100644 apps/st/src/widgets/dnd-tree/ui/custom-node.tsx delete mode 100644 apps/st/src/widgets/dnd-tree/ui/dnd-tree.tsx delete mode 100644 apps/st/src/widgets/dnd-tree/ui/editable-node.tsx delete mode 100644 apps/st/src/widgets/form-wrapper/index.ts delete mode 100644 apps/st/src/widgets/form-wrapper/ui/form-wrapper.tsx delete mode 100644 apps/st/src/widgets/post/api/post-evaluation-service.ts delete mode 100644 apps/st/src/widgets/post/index.ts delete mode 100644 apps/st/src/widgets/post/model/grid-view-table-columns.tsx delete mode 100644 apps/st/src/widgets/post/model/post-evaluation-interface.ts delete mode 100644 apps/st/src/widgets/post/model/post-table-columns.tsx delete mode 100644 apps/st/src/widgets/post/ui/general-file-uploader.tsx delete mode 100644 apps/st/src/widgets/post/ui/post-action.tsx delete mode 100644 apps/st/src/widgets/post/ui/post-comment.tsx delete mode 100644 apps/st/src/widgets/post/ui/post-form.tsx delete mode 100644 apps/st/src/widgets/post/ui/post-page.tsx delete mode 100644 apps/st/src/widgets/post/ui/post.tsx delete mode 100644 apps/st/src/widgets/post/ui/posting-period-selector.tsx delete mode 100644 apps/st/src/widgets/post/ui/sort-select.tsx delete mode 100644 apps/st/src/widgets/post/ui/with-preview-file-uploader.tsx diff --git a/apps/st/app/(without-base-layout)/view/popup/[popupSn]/page.tsx b/apps/st/app/(without-base-layout)/view/popup/[popupSn]/page.tsx deleted file mode 100644 index 16cd8e0af..000000000 --- a/apps/st/app/(without-base-layout)/view/popup/[popupSn]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ViewPopup as VViewPopup } from "@/pages/popup"; - -export default async function ViewPopup({ - params, -}: { - params: Promise<{ popupSn: string }>; -}) { - const { popupSn } = await params; - return ; -} diff --git a/apps/st/app/(without-base-layout)/view/survey-article/[srvySn]/page.tsx b/apps/st/app/(without-base-layout)/view/survey-article/[srvySn]/page.tsx deleted file mode 100644 index 6155852fb..000000000 --- a/apps/st/app/(without-base-layout)/view/survey-article/[srvySn]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ViewSurveyArticle } from "@/pages/survey"; - -export default async function ViewSurveyArticlePage({ - params, -}: { - params: Promise<{ srvySn: string }>; -}) { - const { srvySn } = await params; - return ; -} diff --git a/apps/st/app/(without-base-layout)/view/survey-result/[srvySn]/page.tsx b/apps/st/app/(without-base-layout)/view/survey-result/[srvySn]/page.tsx deleted file mode 100644 index 8ac4b076c..000000000 --- a/apps/st/app/(without-base-layout)/view/survey-result/[srvySn]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ViewSurveyResult } from "@/pages/survey"; - -export default async function ViewSurveyResultPage({ - params, -}: { - params: Promise<{ srvySn: string }>; -}) { - const { srvySn } = await params; - return ; -} diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index a7c697d19..be6adc932 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -22,7 +22,7 @@ if (typeof window !== "undefined") { window.JSZip = JSZip; } -RealGrid.setLicenseKey('upVcPE+wPOmtLjqyBIh9RkM/nBOseBrflwxYpzGZyYm9cY8amGDkiPSoO0A3bI0LS1+jeXPBr4vmDWGWN+mYwx6C1EI62/mceibHCCl3OrbDYCIvknJkCQqgn3X6yKqZI9h9wRtIvbtWMBMOPHCENQuxnxtVqI2reHIsW5fXYGA='); +RealGrid.setLicenseKey(process.env.NEXT_PUBLIC_ST_REALGRID_KEY || ''); const DataGrid = ({ columnInfo, rows = [], rowStartNum, isExcelImport = true, fixedColumns }: DataGridProps) => { const { columns, columnLayout } = columnInfo; diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index 23b1719e4..b1fda5a07 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -24,8 +24,8 @@ const getSidoListFetcher = async (): Promise> => { return statisticsAPi.getSidoList(); }; -const getSggListFetcher = async ([_key, sidoName]: [string, string]): Promise> => { - return statisticsAPi.getSggList(sidoName); +const getSggListFetcher = async (key: [string, string]): Promise> => { + return statisticsAPi.getSggList(key[1]); }; const SearchForm = (props: SearchFormProps = {}) => { diff --git a/apps/st/src/entities/statistics/model/statistics-interface.ts b/apps/st/src/entities/statistics/model/statistics-interface.ts index 0e881cea9..d687df5c2 100644 --- a/apps/st/src/entities/statistics/model/statistics-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-interface.ts @@ -1,5 +1,3 @@ -import { z } from "zod"; - export interface Sido { adstDstrCd: string; sido: string; diff --git a/apps/st/src/shared/lib/match-query-status.ts b/apps/st/src/shared/lib/match-query-status.ts index 0e5f45829..cfefda9fd 100644 --- a/apps/st/src/shared/lib/match-query-status.ts +++ b/apps/st/src/shared/lib/match-query-status.ts @@ -1,3 +1,4 @@ +import type { ReactElement } from "react"; import { type UseQueryResult } from "@tanstack/react-query"; import type { PageableList } from "../model"; @@ -9,24 +10,24 @@ import type { PageableList } from "../model"; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; + Loading?: ReactElement; + Errored?: ReactElement | ((error: unknown) => ReactElement); + Empty?: ReactElement; Success: ( data: UseQueryResult> & { data: NonNullable["data"]>; }, - ) => JSX.Element; + ) => ReactElement; }, -): JSX.Element; +): ReactElement; export function matchQueryStatus( query: UseQueryResult>, options: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: ReactElement; + Errored?: ReactElement | ((error: unknown) => ReactElement); + Success: (data: UseQueryResult>) => ReactElement; }, -): JSX.Element; +): ReactElement; export function matchQueryStatus( query: UseQueryResult>, { @@ -35,12 +36,12 @@ export function matchQueryStatus( Empty, Success, }: { - Loading?: JSX.Element; - Errored?: JSX.Element | ((error: unknown) => JSX.Element); - Empty?: JSX.Element; - Success: (data: UseQueryResult>) => JSX.Element; + Loading?: ReactElement; + Errored?: ReactElement | ((error: unknown) => ReactElement); + Empty?: ReactElement; + Success: (data: UseQueryResult>) => ReactElement; }, -): JSX.Element { +): ReactElement { if (query.isLoading && Loading) { return Loading; } diff --git a/apps/st/src/widgets/dnd-tree/index.ts b/apps/st/src/widgets/dnd-tree/index.ts deleted file mode 100644 index ca1c20f8d..000000000 --- a/apps/st/src/widgets/dnd-tree/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { DndTree } from "./ui/dnd-tree"; -export { EditableNode } from "./ui/editable-node"; diff --git a/apps/st/src/widgets/dnd-tree/ui/custom-node.tsx b/apps/st/src/widgets/dnd-tree/ui/custom-node.tsx deleted file mode 100644 index 11defd25f..000000000 --- a/apps/st/src/widgets/dnd-tree/ui/custom-node.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useEffect, useState } from "react"; -import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview"; -import { Button, cn } from "@common/components"; -import { EditableNode } from "./editable-node"; - -interface TreeData extends Record { - children?: unknown[]; -} - -type CustomNodeProps = { - node: NodeModel; - depth: number; - isOpen: boolean; - isSelected: boolean; - isAdditionalParent: boolean; - treeMethods: TreeMethods | null; - readOnly: boolean; - onToggle: (id: NodeModel["id"]) => void; - onSelect: (node: NodeModel) => void; - updateNodeName: (id: NodeModel["id"], nodeName: string) => Promise; -}; - -function CustomNode({ - node, - depth, - isOpen, - isSelected, - isAdditionalParent, - treeMethods, - readOnly, - onToggle, - onSelect, - updateNodeName, -}: CustomNodeProps) { - const { id, text, data } = node; - const indent = depth * 10 + 5; - - const [editMode, setEditMode] = useState(false); - const [nodeName, setNodeName] = useState(text); - - const handleToggle = () => onToggle(id); - const handleSelect = () => onSelect(node); - - const handleSave = async () => { - await updateNodeName(id, nodeName); - setEditMode(false); - }; - - const handleCancel = () => { - setEditMode(false); - setNodeName(text); - }; - - useEffect(() => { - if (isAdditionalParent) treeMethods?.open(id); - }, [isAdditionalParent]); - - return ( -
- {editMode ? ( - setNodeName(e.target.value)} - onSave={handleSave} - onCancel={handleCancel} - /> - ) : ( - <> -
- {data && data.children && data.children.length > 0 && ( - { - e.stopPropagation(); - handleToggle(); - }} - /> - )} - {text} -
- - - )} -
- ); -} - -export { CustomNode }; diff --git a/apps/st/src/widgets/dnd-tree/ui/dnd-tree.tsx b/apps/st/src/widgets/dnd-tree/ui/dnd-tree.tsx deleted file mode 100644 index b1509006e..000000000 --- a/apps/st/src/widgets/dnd-tree/ui/dnd-tree.tsx +++ /dev/null @@ -1,138 +0,0 @@ -"use client"; - -import React, { type ReactElement, useRef } from "react"; -import { DndProvider } from "react-dnd"; -import { - Tree, - MultiBackend, - getBackendOptions, - type NodeModel, - type TreeMethods, - type DropOptions, - type Classes, -} from "@minoru/react-dnd-treeview"; -import { Button, cn } from "@common/components"; -import { CustomNode } from "./custom-node"; - -interface TreeData extends Record { - children?: unknown[]; -} - -type EditableNodeProps = { depth: number }; - -type DndTreeProps = { - tree: NodeModel[]; - selectedNodeId?: number; - classes?: { - wrapper?: string; - } & Classes; - renderEditableNode?: (props: EditableNodeProps) => ReactElement; - handleSelectNode?: (node: NodeModel) => void; - handleDropNode?: (newTree: NodeModel[], options: DropOptions) => void; - updateNodeName?: (id: NodeModel["id"], nodeName: string) => Promise; -}; - -function DndTree({ - tree, - selectedNodeId, - classes, - renderEditableNode, - handleSelectNode, - handleDropNode, - updateNodeName, -}: DndTreeProps) { - const treeMethods = useRef(null); - - const isAdditionalParent = (node: NodeModel): boolean => { - const additionalNode = tree.find((n) => n.id === -1); - if (!additionalNode) return false; - return additionalNode?.parent === node.id; - }; - - return ( - <> -
- - , { depth, isOpen, onToggle }) => { - if (node.id === -1) - return renderEditableNode?.({ depth }) ?? <>; - return ( - handleSelectNode?.(node)} - updateNodeName={(id, nodeName) => - updateNodeName?.(id, nodeName) ?? Promise.resolve() - } - /> - ); - }} - onDrop={(newTree, options) => handleDropNode?.(newTree, options)} - classes={{ - root: "p-4", - container: "flex flex-col", - dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm", - placeholder: "relative", - ...classes, - }} - sort={false} - dropTargetOffset={5} - insertDroppableFirst={false} - canDrop={(_, { dragSource, dropTargetId }) => { - if (dragSource?.parent === dropTargetId) { - return true; - } - }} - placeholderRender={(_, { depth }) => { - return ( -
- ); - }} - /> - -
-
- - -
- - ); -} - -export { DndTree }; diff --git a/apps/st/src/widgets/dnd-tree/ui/editable-node.tsx b/apps/st/src/widgets/dnd-tree/ui/editable-node.tsx deleted file mode 100644 index f2274f4d8..000000000 --- a/apps/st/src/widgets/dnd-tree/ui/editable-node.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { ComponentProps } from "react"; -import { Button, Input } from "@common/components"; - -type EditableNodeProps = { - depth?: number; - onSave: () => void; - onCancel: () => void; -} & ComponentProps; - -function EditableNode({ - depth, - onSave, - onCancel, - ...props -}: EditableNodeProps) { - return ( -
- -
- - -
-
- ); -} - -export { EditableNode }; diff --git a/apps/st/src/widgets/form-wrapper/index.ts b/apps/st/src/widgets/form-wrapper/index.ts deleted file mode 100644 index aa5addaf9..000000000 --- a/apps/st/src/widgets/form-wrapper/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { FormWrapper } from "./ui/form-wrapper"; diff --git a/apps/st/src/widgets/form-wrapper/ui/form-wrapper.tsx b/apps/st/src/widgets/form-wrapper/ui/form-wrapper.tsx deleted file mode 100644 index b275b6a36..000000000 --- a/apps/st/src/widgets/form-wrapper/ui/form-wrapper.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useRouter } from "next/navigation"; -import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { type FieldValues, type UseFormReturn } from "react-hook-form"; -import { Button } from "@common/components"; -import { Form } from "@/shared/ui/form"; - -type FormWrapperProps = { - form: UseFormReturn; - children: React.ReactNode; - isModifyMode?: boolean; - handleSave: (data: T) => Promise; - handleDelete?: () => Promise; -}; - -function FormWrapper({ - form, - children, - isModifyMode, - handleSave, - handleDelete, -}: FormWrapperProps) { - const router = useRouter(); - const { confirm } = useConfirm(); - - const onSubmit = async (data: T) => { - const confirmMessage = isModifyMode - ? "수정하시겠습니까?" - : "저장하시겠습니까?"; - - if (await confirm(confirmMessage)) { - await handleSave(data); - } - }; - - const onDelete = async () => { - if (await confirm("삭제하시겠습니까?")) { - await handleDelete?.(); - } - }; - - return ( -
- - {children} -
- - - {isModifyMode && handleDelete && ( - - )} -
-
- - ); -} - -export { FormWrapper }; diff --git a/apps/st/src/widgets/post/api/post-evaluation-service.ts b/apps/st/src/widgets/post/api/post-evaluation-service.ts deleted file mode 100644 index 9258ba299..000000000 --- a/apps/st/src/widgets/post/api/post-evaluation-service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { client } from "@/shared/api"; -import { ENDPOINT } from "@/shared/config"; -import type { - PostEvaluationPayload, - PostEvaluationService, -} from "../model/post-evaluation-interface"; - -export class PostEvaluationApiService implements PostEvaluationService { - public async postEvaluation(payload: PostEvaluationPayload) { - await client.post(ENDPOINT.BOARD_SERVICE.POST_EVALUATION, payload); - } -} - -export const PostEvaluationApi = new PostEvaluationApiService(); diff --git a/apps/st/src/widgets/post/index.ts b/apps/st/src/widgets/post/index.ts deleted file mode 100644 index feb886602..000000000 --- a/apps/st/src/widgets/post/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { Post } from "./ui/post"; -export { PostForm } from "./ui/post-form"; -export { PostPage } from "./ui/post-page"; -export { SortSelect } from "./ui/sort-select"; -export { GeneralFileUploader } from "./ui/general-file-uploader"; diff --git a/apps/st/src/widgets/post/model/grid-view-table-columns.tsx b/apps/st/src/widgets/post/model/grid-view-table-columns.tsx deleted file mode 100644 index db67fbd6e..000000000 --- a/apps/st/src/widgets/post/model/grid-view-table-columns.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import dayjs from "dayjs"; -import Image from "next/image"; -import { useQuery } from "@tanstack/react-query"; -import type { ColumnDef } from "@tanstack/react-table"; -import { Badge, Checkbox, Skeleton, cn } from "@common/components"; -import { AttachmentApi } from "@/entities/attachment"; -import type { Post } from "@/entities/post"; -import type { BbsType } from "@/shared/store"; -import { ENDPOINT } from "@/shared/config"; - -declare module "@tanstack/react-table" { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface TableMeta { - bbsTypeCd?: BbsType; - } -} - -export const createColumns = ( - thumbRefs: React.MutableRefObject>, -): ColumnDef[] => [ - { - id: "select", - header: ({ table }) => ( - e.stopPropagation()} - onCheckedChange={(checked) => - table.toggleAllPageRowsSelected(!!checked) - } - className="h-4 w-4 rounded border-gray-300" - /> - ), - cell: ({ row, table }) => { - const { atflCd, pstNo, pstTtl, pstCn, inqNocs, rgtrNm, regDt } = - row.original; - const bbsTypeCd = table.options.meta?.bbsTypeCd; - - if (bbsTypeCd === "BBSIMAGE") { - return ( -
-
- e.stopPropagation()} - onCheckedChange={(checked) => row.toggleSelected(!!checked)} - className="h-4 w-4 rounded border-gray-300" - /> - {inqNocs >= 100 && HOT} - {dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && ( - NEW - )} - - {pstTtl} - -
- -
- - {rgtrNm} - - - {dayjs(regDt).format("YYYY-MM-DD")} - -
-
- ); - } - - if (bbsTypeCd === "BBSMOVIE") { - return ( -
- -
-
-
- e.stopPropagation()} - onCheckedChange={(checked) => row.toggleSelected(!!checked)} - className="h-4 w-4 rounded border-gray-300" - /> - {inqNocs >= 100 && HOT} - {dayjs().diff(dayjs(regDt), "hour") < 24 && ( - NEW - )} - - {pstTtl} - -
-

- {pstCn} -

-
- - {rgtrNm} - - - {dayjs(regDt).format("YYYY-MM-DD")} - -
-
-
-
- ); - } - }, - }, -]; - -type ThumbImageProps = { - atflCd: string; - pstNo: number; - thumbRefs: React.MutableRefObject>; - bbsTypeCd?: string; - classes?: { - image?: string; - }; -}; - -const ThumbImage = ({ - atflCd, - pstNo, - thumbRefs, - bbsTypeCd, - classes, -}: ThumbImageProps) => { - const prevImage = thumbRefs.current.get(pstNo); - - const { data, isLoading } = useQuery({ - queryKey: [ENDPOINT.ATTACH_SERVICE.IMAGES, 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; - }, - enabled: !prevImage && !!atflCd, - }); - - const imageUrl = prevImage ?? data; - - if (isLoading) - return ( -
- -
- ); - - if (imageUrl) - return ( -
- 썸네일 이미지 -
- ); - - return ( -
- -
- ); -}; diff --git a/apps/st/src/widgets/post/model/post-evaluation-interface.ts b/apps/st/src/widgets/post/model/post-evaluation-interface.ts deleted file mode 100644 index c99249d6a..000000000 --- a/apps/st/src/widgets/post/model/post-evaluation-interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface PostEvaluationPayload { - pstNo: number; - evlIp?: string; - evlDt?: string; - evlScr: number; -} - -export interface PostEvaluationService { - postEvaluation: (payload: PostEvaluationPayload) => Promise; -} diff --git a/apps/st/src/widgets/post/model/post-table-columns.tsx b/apps/st/src/widgets/post/model/post-table-columns.tsx deleted file mode 100644 index 596fbc1a8..000000000 --- a/apps/st/src/widgets/post/model/post-table-columns.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import dayjs from "dayjs"; -import type { ColumnDef } from "@tanstack/react-table"; -import { Badge, Button, Checkbox } from "@common/components"; -import { AttachmentApi } from "@/entities/attachment"; -import type { Post } from "@/entities/post"; - -export const columns: ColumnDef[] = [ - { - id: "select", - header: ({ table }) => ( - e.stopPropagation()} - onCheckedChange={(checked) => - table.toggleAllPageRowsSelected(!!checked) - } - className="h-4 w-4 rounded border-gray-300" - /> - ), - cell: ({ row }) => ( - e.stopPropagation()} - onCheckedChange={(checked) => row.toggleSelected(!!checked)} - className="h-4 w-4 rounded border-gray-300" - /> - ), - }, - { - accessorKey: "rowNo", - header: "NO", - cell: ({ row, table }) => { - if (row.original.isUpndFixPost) { - return ( - - - - ); - } - - const totalRows = table.getRowCount(); - const currentPage = table.getState().pagination.pageIndex; - const pageSize = table.getState().pagination.pageSize; - return totalRows - (currentPage * pageSize + row.index); - }, - }, - { - accessorKey: "pstTtl", - header: "제목", - cell: ({ row }) => { - const { ansPstn = 0, pstTtl, inqNocs, cmntCnt } = row.original; - const indentPadding = ansPstn > 0 ? (ansPstn - 1) * 20 : 0; - - return ( -
- {ansPstn > 0 && ( -
-
- -
- 답변 -
- )} - {inqNocs >= 100 && HOT} - {dayjs().diff(dayjs(row.original.regDt), "hour") < 24 && ( - NEW - )} - {pstTtl} - {cmntCnt > 0 && ( -
- - {cmntCnt} -
- )} -
- ); - }, - }, - { - accessorKey: "inqNocs", - header: "조회수", - }, - { - accessorKey: "atflCd", - header: "첨부파일", - cell: ({ row }) => { - const atflCd = row.original.atflCd; - if (!atflCd) return null; - return ; - }, - }, - { - accessorKey: "rgtrNm", - header: "작성자", - }, - { - accessorKey: "regDt", - header: "등록일", - cell: ({ row }) => dayjs(row.original.regDt).format("YYYY-MM-DD"), - }, -]; - -const AttachmentButton = ({ atflCd }: { atflCd: string }) => { - const handleDownload = async (e: React.MouseEvent) => { - e.stopPropagation(); - try { - let filename = ""; - const response = await AttachmentApi.downloadAttachmentByAtflCd(atflCd); - const contentDisposition = response.headers["content-disposition"]; - if (contentDisposition) { - const filenameMatch = contentDisposition.match( - /filename\*=UTF-8''([^;]+)/, - ); - if (filenameMatch) { - filename = decodeURIComponent(filenameMatch[1]); - } - } - - const blob = new Blob([response.data]); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - - window.URL.revokeObjectURL(url); - document.body.removeChild(link); - } catch (error) { - console.error(error); - } - }; - - return ( - - ); -}; diff --git a/apps/st/src/widgets/post/ui/general-file-uploader.tsx b/apps/st/src/widgets/post/ui/general-file-uploader.tsx deleted file mode 100644 index cbd77ca18..000000000 --- a/apps/st/src/widgets/post/ui/general-file-uploader.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useEffect, useState } from "react"; -import type { FieldPath, UseFormReturn } from "react-hook-form"; -import type { HttpResponse, Upload } from "tus-js-client"; -import { Button, FileUploader } from "@common/components"; -import { handleTusUpload } from "@/entities/tus"; -import { type Attachment } from "@/entities/attachment"; -import { setCookie, useAlertStore, useMenuStore } from "@/shared/lib"; - -function GeneralFileUploader({ - form, - attachmentData, - deleteAttachmentIds, - setDeleteAttachmentIds, -}: { - form: UseFormReturn; - attachmentData?: Attachment[]; - deleteAttachmentIds: string[]; - setDeleteAttachmentIds: (deleteAttachmentIds: string[]) => void; -}) { - const alert = useAlertStore((state) => state.setMessage); - const bbsInfo = useMenuStore((state) => state.bbsInfo); - - const [attachments, setAttachments] = useState>( - {}, - ); - - const handleTusResponse = (res: HttpResponse, upload: Upload) => { - const fileInfo = res.getHeader("X-File-Info"); - if ( - typeof fileInfo !== "string" || - fileInfo === undefined || - fileInfo === null - ) - return; - - const { atflCd, atflId } = JSON.parse(fileInfo) ?? {}; - if (atflCd) form.setValue("atflCd" as FieldPath, atflCd); - if (atflId) { - setAttachments((prev) => ({ - ...prev, - [atflId]: { - atflCd, - atflId, - orgnlFileNm: (upload.file as File).name, - atflFlieSz: (upload.file as File).size, - atflType: (upload.file as File).type, - }, - })); - } - }; - - const onUpload = (e: React.ChangeEvent) => { - const atflCd = form.getValues("atflCd" as FieldPath); - 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, - }); - } - }; - - useEffect(() => { - const mapper = (data: Attachment[]) => - data.reduce( - (acc, curr) => { - acc[curr.atflId] = curr; - return acc; - }, - {} as Record, - ); - if (attachmentData) setAttachments(mapper(attachmentData)); - }, [attachmentData]); - - return ( - - {Object.entries(attachments).map(([atflId, attachment]) => { - if (deleteAttachmentIds.includes(atflId)) return null; - return ( -
- {attachment.orgnlFileNm} - -
- ); - })} -
- } - /> - ); -} - -export { GeneralFileUploader }; diff --git a/apps/st/src/widgets/post/ui/post-action.tsx b/apps/st/src/widgets/post/ui/post-action.tsx deleted file mode 100644 index 860d8dad8..000000000 --- a/apps/st/src/widgets/post/ui/post-action.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { toast } from "sonner"; -import { useEffect } from "react"; -import { useRouter, usePathname } from "next/navigation"; -import { useClipboard } from "@frontend-opensource/use-react-hooks"; -import { Button } from "@common/components"; -import { deleteReasonMessages } from "@/entities/post"; -import { AuthorityButton } from "@/entities/menu-authority"; -import { useMenuStore, useSelectPrompt, useUserStore } from "@/shared/lib"; - -type PostActionProps = { - bbsId: string; - pstNo: string; - children: React.ReactNode; - handleDelete: (delRsn?: string) => Promise; -}; - -const selectData = Object.entries(deleteReasonMessages).map( - ([label, value]) => ({ label, value }), -); - -function PostAction({ children, handleDelete }: PostActionProps) { - const router = useRouter(); - const pathname = usePathname(); - const { copyText, copied } = useClipboard(); - const { prompt } = useSelectPrompt(selectData); - const bbsInfo = useMenuStore((state) => state.bbsInfo); - const userInfo = useUserStore((state) => state.userInfo); - const postPageLink = `/bbs/${bbsInfo?.bbsTypeCd.toLowerCase()}/${bbsInfo?.bbsId}`; - - const onDelete = async () => { - const { resolved, value } = await prompt("삭제 사유를 입력해주세요."); - if (resolved) handleDelete(value); - }; - - const checkAnsUseAuthrt = () => { - // 관리자 - if (bbsInfo?.ansUseAuthrt === "A") { - return userInfo?.adminRoleYn === "Y"; - } - // 회원 - if (bbsInfo?.ansUseAuthrt === "M") { - return !!userInfo; - } - // 비회원 - return true; - }; - - useEffect(() => { - if (copied) toast.success("URL이 클립보드에 복사되었습니다."); - }, [copied]); - - return ( - <> - {children} -
- - - router.push(`${pathname}/modify`)} - > - 수정 - - {bbsInfo?.ansUseYn === "Y" && ( - router.push(`${pathname}/answer`)} - disabled={!checkAnsUseAuthrt()} - > - 답글 - - )} - - 삭제 - -
- - ); -} - -export { PostAction }; diff --git a/apps/st/src/widgets/post/ui/post-comment.tsx b/apps/st/src/widgets/post/ui/post-comment.tsx deleted file mode 100644 index 0e96ae1cb..000000000 --- a/apps/st/src/widgets/post/ui/post-comment.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import dayjs from "dayjs"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { - Button, - Input, - Label, - RadioGroup, - RadioGroupItem, - Textarea, -} from "@common/components"; -import { - CommentApi, - commentFormSchema, - type CommentFormData, - type CommentUpdateFormData, -} from "@/entities/comment"; -import { ENDPOINT } from "@/shared/config"; -import { - useAlertStore, - useMenuStore, - useMutationWrapper, - usePrompt, - useUserStore, -} from "@/shared/lib"; -import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; - -function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) { - const { prompt } = usePrompt(); - const { confirm } = useConfirm(); - const queryClient = useQueryClient(); - const bbsInfo = useMenuStore((state) => state.bbsInfo); - const userInfo = useUserStore((state) => state.userInfo); - const alert = useAlertStore((state) => state.setMessage); - const [editValue, setEditValue] = useState(""); - const [editingCmntNo, setEditingCmntNo] = useState(null); - - const { data: comments } = useQuery({ - queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo], - queryFn: () => CommentApi.getComments({ pstNo }), - enabled: !!pstNo, - }); - - const { mutate: createComment } = useMutationWrapper({ - mutationFn: (data: CommentFormData) => CommentApi.createComment(data), - onSuccess: () => { - alert("등록되었습니다."); - form.setValue("cmntCn", ""); - queryClient.invalidateQueries({ - queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS], - }); - }, - }); - - const { mutate: updateComment } = useMutationWrapper({ - mutationFn: ({ - cmntNo, - data, - }: { - cmntNo: number; - data: CommentUpdateFormData; - }) => CommentApi.updateComment(bbsId, pstNo, cmntNo, data), - onSuccess: () => { - alert("수정되었습니다."); - setEditingCmntNo(null); - setEditValue(""); - queryClient.invalidateQueries({ - queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS], - }); - }, - }); - - const { mutate: deleteComment } = useMutationWrapper({ - mutationFn: ({ cmntNo, cmntPswd }: { cmntNo: number; cmntPswd: string }) => - CommentApi.deleteComment(bbsId, pstNo, cmntNo, cmntPswd), - onSuccess: () => { - alert("삭제되었습니다."); - queryClient.invalidateQueries({ - queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS], - }); - }, - }); - - const form = useForm({ - resolver: zodResolver(commentFormSchema), - values: { - pstNo, - cmntCn: "", - wrtrNm: userInfo?.userName ?? "", - cmntPswd: userInfo?.userUnqId ?? "", - }, - }); - - const onSubmit = (data: CommentFormData) => { - createComment(data); - }; - - const getCommentPassword = async () => { - let cmntPswd = ""; - if (userInfo?.userUnqId) { - cmntPswd = userInfo?.userUnqId; - } else { - const { resolved, value } = await prompt("비밀번호를 입력해주세요."); - if (resolved) cmntPswd = value ?? ""; - } - return cmntPswd; - }; - - const handleUpdateComment = async (cmntNo: number, wrtrNm: string) => { - if (!(await confirm("수정하시겠습니까?"))) return; - const cmntPswd = await getCommentPassword(); - updateComment({ - cmntNo, - data: { - cmntCn: editValue, - wrtrNm, - cmntPswd, - }, - }); - }; - - const handleDeleteComment = async (cmntNo: number) => { - if (!(await confirm("삭제하시겠습니까?"))) return; - const cmntPswd = await getCommentPassword(); - deleteComment({ cmntNo, cmntPswd }); - }; - - return ( -
-
- - {bbsInfo?.cmntUseYn === "C" && ( - ( - - - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- )} - /> - )} - ( - - -