feat: post 폴더 추가(cs)

This commit is contained in:
2025-05-13 17:37:39 +09:00
parent 2352a395fb
commit d1400a6908
65 changed files with 3010 additions and 8 deletions
+3
View File
@@ -8,6 +8,7 @@ export const ENDPOINT = {
EDITOR_UPLOAD: "/attach-service/api/v1/upload/editor",
IMAGES: "/attach-service/api/v1/images",
ATTACHMENTS: "/attach-service/api/v1/atfl/attachments",
DOWNLOAD: "/attach-service/api/v1/atflcd/download",
},
USER_SERVICE: {
LOGIN: "/user-service/login",
@@ -37,6 +38,8 @@ 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_COMMENTS: "/board-service/api/v1/posts-cmnts",
},
};
+2
View File
@@ -1,3 +1,5 @@
export { getCookie, setCookie, removeCookie } from "./cookie";
export { useAlertStore } from "./use-alert-store";
export { useMenuStore } from "./use-menu-store";
export { useUserStore } from "./use-user-store";
export { usePrompt } from "./use-prompt";
+26
View File
@@ -0,0 +1,26 @@
import { useContext } from "react";
import { PromptContext } from "../providers/prompt-provider";
export const usePrompt = () => {
const { message, resolve, setMessage, setResolve } =
useContext(PromptContext);
const prompt = (message: string) => {
setMessage(message);
return new Promise<string>((resolve) => {
setResolve(() => resolve);
});
};
const onSubmit = (value: string) => {
resolve?.(value);
setMessage("");
};
const onCancel = () => {
resolve?.("");
setMessage("");
};
return { message, isOpen: !!message, prompt, onSubmit, onCancel };
};
@@ -0,0 +1,28 @@
import { createContext, useState } from "react";
interface PromptContext {
message?: string;
resolve?: (value: string) => void;
setMessage: (message: string) => void;
setResolve: (resolve: (value: string) => void) => void;
}
export const PromptContext = createContext<PromptContext>({
message: "",
resolve() {},
setMessage() {},
setResolve() {},
});
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
const [message, setMessage] = useState<string>();
const [resolve, setResolve] = useState<(value: string) => void>();
return (
<PromptContext.Provider
value={{ message, resolve, setMessage, setResolve }}
>
{children}
</PromptContext.Provider>
);
};
+74 -4
View File
@@ -1,23 +1,93 @@
import { createStore } from "zustand/vanilla";
import type { MenuItem } from "@/entities/menu";
export const BBS_TYPE = {
BBSNOTICE: "BBSNOTICE",
BBSNORMAL: "BBSNORMAL",
BBSFAQ: "BBSFAQ",
BBSIMAGE: "BBSIMAGE",
BBSMOVIE: "BBSMOVIE",
} as const;
export type BbsType = (typeof BBS_TYPE)[keyof typeof BBS_TYPE];
export interface IBbs {
bbsId: number;
bbsTypeCd: BbsType;
bbsTypeNm: string;
bbsNm: string;
bbsExpln: string;
rfrncUseYn: string;
atflUseYn: string;
atflMaxCnt: number;
atflMaxFileSz: number;
ansUseYn: string;
ansUseAuthrt: string;
cmntUseYn: string;
dgstfnEvlUseYn: string;
smsUseYn: string;
upndFixUseYn: string;
pstgPrdUseYn: string;
popupUseYn: string;
delYn: string;
useYn: string;
mdfcnDt: string;
mdfrNm: string;
}
interface IMenu {
menuCd: number;
menuKornNm: string;
menuEngNm: string;
parentId: number | null;
sortSeq: number;
iconNm?: string;
children: IMenu[] | null;
levelNo: number;
urlPath: string;
menuLnkgTypeCd: string;
menuLnkgSn: number;
menuPath: string;
qrcd: string;
useYn: string;
otptYn: string;
newWndwYn: string;
requiredUrlPath: boolean;
}
interface IAuthority {
mngAuthrtYn: string;
inqAuthrtYn: string;
regAuthrtYn: string;
mdfcnAuthrtYn: string;
delAuthrtYn: string;
}
export type MenuState = {
currentMenuInfo: MenuItem | null;
bbsInfo: IBbs | null;
currentMenuInfo: IMenu | null;
pageMenuAuthority: IAuthority | null;
};
export type MenuActions = {
setCurrentMenuInfo: (menu: MenuItem) => void;
setBbsInfo: (bbs: IBbs) => void;
setCurrentMenuInfo: (menu: IMenu) => void;
setPageMenuAuthority: (authority: IAuthority) => void;
};
export type MenuStore = MenuState & MenuActions;
export const defaultInitState: MenuState = {
currentMenuInfo: null,
bbsInfo: null,
pageMenuAuthority: null,
};
export const createMenuStore = (initState: MenuState = defaultInitState) => {
return createStore<MenuStore>()((set) => ({
...initState,
setCurrentMenuInfo: (menu: MenuItem) => set({ currentMenuInfo: menu }),
setBbsInfo: (bbs: IBbs) => set({ bbsInfo: bbs }),
setCurrentMenuInfo: (menu: IMenu) => set({ currentMenuInfo: menu }),
setPageMenuAuthority: (authority: IAuthority) =>
set({ pageMenuAuthority: authority }),
}));
};
+1
View File
@@ -5,6 +5,7 @@ export interface IUserInfo {
mdfcnDt: string;
rgtr: string;
mdfr: string;
adminRoleYn: string;
userUnqId: string;
userId: string;
userName: string;
@@ -0,0 +1,158 @@
import type { HTMLAttributes } from "react";
import {
flexRender,
type RowData,
type Table as TableInstance,
} from "@tanstack/react-table";
import {
ChevronLeftIcon,
ChevronRightIcon,
DoubleArrowLeftIcon,
DoubleArrowRightIcon,
} from "@radix-ui/react-icons";
import { Button, Checkbox, Label, cn } from "@common/components";
declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ColumnMeta<TData extends RowData, TValue> {
align?: "left" | "center" | "right";
rowSpan?: number;
}
}
type GridViewDataTableProps<T> = {
table: TableInstance<T>;
variant?: "default" | "secondary" | "line";
className?: HTMLAttributes<HTMLTableElement>["className"];
viewPagination?: boolean;
onRowClick?: (row: T) => void;
};
function GridViewDataTable<T>({
table,
className,
viewPagination,
onRowClick,
}: GridViewDataTableProps<T>) {
return (
<>
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border p-4">
<Checkbox
id="all-select"
checked={table.getIsAllPageRowsSelected()}
onClick={(e) => e.stopPropagation()}
onCheckedChange={(checked) =>
table.toggleAllPageRowsSelected(!!checked)
}
/>
<Label htmlFor="all-select" className="text-2xl">
전체선택
</Label>
</div>
<div className={cn("w-full pb-4", className)}>
<div className="grid grid-cols-5 gap-6">
{table.getRowModel().rows.map((row) => (
<div
key={row.id}
onClick={() => onRowClick?.(row.original)}
className={cn(
"rounded-md border border-border p-4 shadow-md",
onRowClick && "cursor-pointer hover:bg-accent",
)}
>
{row.getVisibleCells().map((cell) => {
const { meta } = cell.column.columnDef;
return (
<div key={cell.id} {...meta}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
);
})}
</div>
))}
</div>
</div>
{viewPagination && <Pagination table={table} />}
</>
);
}
function Pagination<T>({ table }: { table: TableInstance<T> }) {
const pageCount = table.getPageCount();
const currentPage = table.getState().pagination.pageIndex;
const maxVisiblePages = 10; // 한 번에 보여줄 최대 페이지 수
let startPage = Math.max(0, currentPage - maxVisiblePages / 2); // 시작 페이지
const endPage = Math.min(pageCount, startPage + maxVisiblePages); // 끝 페이지
if (endPage - startPage < maxVisiblePages) {
startPage = Math.max(0, endPage - maxVisiblePages);
}
const pageNumbers = Array(endPage - startPage)
.fill(0)
.map((_, i) => startPage + i);
return (
<div className="mt-[24px] flex items-center justify-center space-x-2">
<Button
variant="ghost"
color="white"
className="hidden size-10 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">첫 페이지로</span>
<DoubleArrowLeftIcon className="size-4" />
</Button>
<Button
variant="ghost"
color="white"
className="size-10 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">이전 페이지로</span>
<ChevronLeftIcon className="size-4" />
</Button>
<div className="flex items-center justify-center text-sm font-medium">
{pageNumbers.map((pageNumber) => (
<Button
key={pageNumber}
variant="ghost"
className={cn(
"inline-block size-10 !bg-transparent p-0 text-foreground hover:!bg-accent",
currentPage === pageNumber &&
"!bg-primary !font-bold !text-primary-foreground hover:!bg-primary",
)}
onClick={() => table.setPageIndex(pageNumber)}
>
{pageNumber + 1}
</Button>
))}
</div>
<Button
variant="ghost"
color="white"
className="size-10 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">다음 페이지로</span>
<ChevronRightIcon className="size-4" />
</Button>
<Button
variant="ghost"
color="white"
className="hidden size-10 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">마지막 페이지로</span>
<DoubleArrowRightIcon className="size-4" />
</Button>
</div>
);
}
export { GridViewDataTable };