feat: post 폴더 추가(cs)
This commit is contained in:
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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 }),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user