Merge branch 'iipbs/common/main' into iipbs/sn/ii
This commit is contained in:
Generated
+8
@@ -0,0 +1,8 @@
|
||||
# 디폴트 무시된 파일
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 에디터 기반 HTTP 클라이언트 요청
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
@@ -77,7 +77,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>제목</TableHead>
|
||||
<TableHead data-required>제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -120,7 +120,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
</TableRow>
|
||||
{!userInfo?.userUnqId && (
|
||||
<TableRow>
|
||||
<TableHead>비밀번호</TableHead>
|
||||
<TableHead data-required>비밀번호</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -44,7 +44,9 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
const thumbRefs = useRef<Map<number, string>>(new Map());
|
||||
|
||||
const [tableSort, setTableSort] = useState("");
|
||||
const [postParams, setPostParams] = useState<PostParams>({
|
||||
const [keywordType, setKeywordType] = useState("title");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<PostParams>({
|
||||
keywordType: "title",
|
||||
keyword: "",
|
||||
});
|
||||
@@ -58,7 +60,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
|
||||
queryFn: () =>
|
||||
PostApi.getPostList(parseInt(bbsId), {
|
||||
...postParams,
|
||||
...searchParams,
|
||||
upndFixYn: bbsInfo?.upndFixUseYn,
|
||||
}),
|
||||
select: (data) => {
|
||||
@@ -79,10 +81,16 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
isLoading,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pagination, tableSort],
|
||||
queryKey: [
|
||||
ENDPOINT.BOARD_SERVICE.POSTS_USER,
|
||||
bbsId,
|
||||
searchParams,
|
||||
pagination,
|
||||
tableSort,
|
||||
],
|
||||
queryFn: () =>
|
||||
PostApi.getPostList(parseInt(bbsId), {
|
||||
...postParams,
|
||||
...searchParams,
|
||||
ansUseYn: bbsInfo?.ansUseYn,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
@@ -128,7 +136,29 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType,
|
||||
keyword,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeywordType("title");
|
||||
setKeyword("");
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType: "title",
|
||||
keyword: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Post) => {
|
||||
@@ -141,12 +171,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={postParams.keywordType}
|
||||
onValueChange={(keywordType) => {
|
||||
setPostParams({ ...postParams, keywordType });
|
||||
}}
|
||||
>
|
||||
<Select value={keywordType} onValueChange={setKeywordType}>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
@@ -157,15 +182,21 @@ function PostPage({ bbsId }: { bbsId: string }) {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={postParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPostParams({ ...postParams, keyword: e.target.value })
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
PORT=13000
|
||||
LOG_FILE="start_13000.log"
|
||||
|
||||
# 1. 기존 포트 사용 프로세스 종료
|
||||
PID=$(lsof -t -i :$PORT)
|
||||
if [ -n "$PID" ]; then
|
||||
echo "Killing process on port $PORT (PID: $PID)"
|
||||
kill -9 $PID
|
||||
fi
|
||||
|
||||
# 2. 백그라운드로 앱 시작 (로그 파일로 리디렉션)
|
||||
echo "Starting app on port $PORT..."
|
||||
nohup pnpm start > $LOG_FILE 2>&1 &
|
||||
|
||||
# 3. PID 확인
|
||||
sleep 1
|
||||
NEW_PID=$(lsof -t -i :$PORT)
|
||||
if [ -n "$NEW_PID" ]; then
|
||||
echo "App started successfully (PID: $NEW_PID)"
|
||||
echo "Logs: tail -f $LOG_FILE"
|
||||
else
|
||||
echo "Failed to start app. Check logs: $LOG_FILE"
|
||||
fi
|
||||
@@ -9,12 +9,11 @@ import type {
|
||||
export class ApiLogApiService implements ApiLogService {
|
||||
public async getApiLogList(params: ApiLogParams): Promise<ApiLogList> {
|
||||
const response = await client.get<ApiLogList>(
|
||||
`${ENDPOINT.USER_SERVICE.APILOG}`,
|
||||
`${ENDPOINT.CMS_SERVICE.APILOG}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const ApiLogApi = new ApiLogApiService();
|
||||
|
||||
@@ -2,8 +2,9 @@ import type { PageableList, PageableParams } from "@/shared/model";
|
||||
|
||||
export interface ApiLogParams extends PageableParams {
|
||||
siteId?: string;
|
||||
userId?: string;
|
||||
userUnqId?: string;
|
||||
userNm?: string;
|
||||
userId?: string;
|
||||
apiHttpPmhd?: string;
|
||||
dmndUrl?: string;
|
||||
ipAddr?: string;
|
||||
|
||||
@@ -85,6 +85,15 @@ export class ContentsApiService implements ContentsService {
|
||||
`${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}/${contsSn}/${contsVer}/approve`,
|
||||
);
|
||||
}
|
||||
|
||||
public async rejectContents(
|
||||
contsSn: number,
|
||||
contsVer: number,
|
||||
): Promise<void> {
|
||||
await client.put(
|
||||
`${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}/${contsSn}/${contsVer}/reject`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ContentsApi = new ContentsApiService();
|
||||
|
||||
@@ -97,4 +97,5 @@ export interface ContentsService {
|
||||
deleteContents(contsSn: number, contsVer: number): Promise<void>;
|
||||
requestApprove(contsSn: number, contsVer: number): Promise<void>;
|
||||
approveContents(contsSn: number, contsVer: number): Promise<void>;
|
||||
rejectContents(contsSn: number, contsVer: number): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { PageableList, PageableParams } from "@/shared/model";
|
||||
// 그룹 사용자 검색 파라미터 인터페이스
|
||||
export interface GroupUserSearchParams extends PageableParams {
|
||||
keyword?: string;
|
||||
userNm?: string;
|
||||
}
|
||||
|
||||
// 그룹 사용자 목록 응답 인터페이스
|
||||
|
||||
@@ -3,4 +3,5 @@ export {
|
||||
groupFormSchema,
|
||||
type GroupFormData,
|
||||
type GroupListResponseDto,
|
||||
type GroupSearchParams,
|
||||
} from "./model/group-interface";
|
||||
|
||||
@@ -56,7 +56,10 @@ export const groupFormSchema = z.object({
|
||||
groupNm: z.string().min(1, { message: "그룹명을 입력해주세요." }),
|
||||
groupSeCd: z.string().min(1, { message: "그룹구분을 선택해주세요." }),
|
||||
groupExpln: z.string().optional(),
|
||||
delYn: z.string().default("N"),
|
||||
delYn: z
|
||||
.string()
|
||||
.transform((val) => (val === "" || val === undefined ? "N" : val))
|
||||
.default("N"),
|
||||
authrtId: z.string().min(1, { message: "권한ID를 선택해주세요." }),
|
||||
siteId: z.string().optional(),
|
||||
mngrYn: z.string().default("N"),
|
||||
|
||||
@@ -74,7 +74,7 @@ function ManagementAuthorityDialog({ menuCd }: ManagementAuthorityDialogProps) {
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="gap-3" disabled={!menuCd}>
|
||||
<i className="diveicon di-personal-information" />
|
||||
<i className="diveicon di-key" />
|
||||
권한관리
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { MenuItem, MenuService } from "../model/menu-interface";
|
||||
import type {
|
||||
MenuService,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuFormData,
|
||||
MenuTemporarilyStoredData,
|
||||
} from "../model/menu-interface";
|
||||
|
||||
export class MenuApiService implements MenuService {
|
||||
async getMenuRoles(siteId: string): Promise<MenuItem[]> {
|
||||
@@ -9,6 +15,56 @@ export class MenuApiService implements MenuService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMenuInfo(menuCd: number): Promise<Menu> {
|
||||
const response = await client.get<Menu>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createMenu(data: MenuFormData): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenu(
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteMenu(menuCd: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`);
|
||||
}
|
||||
|
||||
public async menuTemporaryStorage(
|
||||
menuCd: number,
|
||||
data: MenuFormData,
|
||||
): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_TEMPORARY_STORAGE}/${menuCd}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getMenuTemporarilyStoredData(
|
||||
menuCd: number,
|
||||
): Promise<MenuTemporarilyStoredData[]> {
|
||||
const response = await client.get<MenuTemporarilyStoredData[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_TEMPORARY_STORAGE}/menu/${menuCd}/user`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuApi = new MenuApiService();
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
export { MenuApi } from "./api/menu-service";
|
||||
export {
|
||||
MENU_TYPE,
|
||||
menuFormSchema,
|
||||
type MenuType,
|
||||
type MenuItem,
|
||||
type MenuFormData,
|
||||
} from "./model/menu-interface";
|
||||
export { MenuWrapper } from "./lib/menu-wrapper";
|
||||
export type { MenuItem } from "./model/menu-interface";
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MENU_TYPE = {
|
||||
EMPTY: "EMPTY",
|
||||
CONTENTS: "CONTENTS",
|
||||
BOARD: "BOARD",
|
||||
INSIDE: "INSIDE",
|
||||
OUTSIDE: "OUTSIDE",
|
||||
BOTTOM: "BOTTOM",
|
||||
} as const;
|
||||
|
||||
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
|
||||
|
||||
export interface MenuItem {
|
||||
menuCd: number;
|
||||
menuKornNm: string;
|
||||
@@ -18,6 +31,65 @@ export interface MenuItem {
|
||||
requiredUrlPath: boolean;
|
||||
}
|
||||
|
||||
export interface MenuTemporarilyStoredData {
|
||||
tmprStrgSn: number;
|
||||
menuCd: number;
|
||||
menuTmprStrg: string;
|
||||
regDt: string;
|
||||
rgtr: string;
|
||||
mdfcnDt: string;
|
||||
mdfr: string;
|
||||
}
|
||||
|
||||
export const menuFormSchema = z.object({
|
||||
siteId: z.string(),
|
||||
menuKornNm: z.string(),
|
||||
menuSubKornNm: z.string().nullish(),
|
||||
menuEngNm: z.string().nullish(),
|
||||
menuSubEngNm: z.string().nullish(),
|
||||
levelNo: z.number(),
|
||||
urlPath: z.string().nullish(),
|
||||
iconNm: z.string().nullish(),
|
||||
menuLnkgTypeCd: z.enum([
|
||||
MENU_TYPE.EMPTY,
|
||||
MENU_TYPE.CONTENTS,
|
||||
MENU_TYPE.BOARD,
|
||||
MENU_TYPE.INSIDE,
|
||||
MENU_TYPE.OUTSIDE,
|
||||
MENU_TYPE.BOTTOM,
|
||||
]),
|
||||
menuLnkgSn: z.number().nullish(),
|
||||
otptYn: z.string().default("Y"),
|
||||
prntMenuCd: z.number().nullish(),
|
||||
sortSeq: z.number(),
|
||||
evlUseYn: z.string().default("Y"),
|
||||
qrcd: z.string().nullish(),
|
||||
useYn: z.string().default("Y"),
|
||||
newWndwYn: z.string().nullish(),
|
||||
menuLnkgSnNm: z.string().nullish(),
|
||||
menuPicUserUnqId: z.string().nullish(),
|
||||
menuPicUserUnqIdNm: z.string().nullish(),
|
||||
menuPicDsgnYmd: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type MenuFormData = z.infer<typeof menuFormSchema>;
|
||||
|
||||
export type Menu = Omit<MenuFormData, "iconNm"> & {
|
||||
menuCd: number;
|
||||
iconName?: string;
|
||||
};
|
||||
|
||||
export interface MenuService {
|
||||
getMenuRoles(siteId: string): Promise<MenuItem[]>;
|
||||
getMenuInfo: (menuCd: number) => Promise<Menu>;
|
||||
createMenu: (data: MenuFormData) => Promise<void>;
|
||||
updateMenu: (
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
) => Promise<void>;
|
||||
deleteMenu: (menuCd: number) => Promise<void>;
|
||||
menuTemporaryStorage: (menuCd: number, data: MenuFormData) => Promise<void>;
|
||||
getMenuTemporarilyStoredData: (
|
||||
menuCd: number,
|
||||
) => Promise<MenuTemporarilyStoredData[]>;
|
||||
}
|
||||
|
||||
@@ -38,10 +38,14 @@ function AdminPage() {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const [adminQuery, setAdminQuery] = useState<UserSearchParams>({
|
||||
const [keywordType, setKeywordType] = useState("");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [grpId, setGrpId] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<UserSearchParams>({
|
||||
keywordType: "",
|
||||
keyword: "",
|
||||
grpId: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -66,10 +70,10 @@ function AdminPage() {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
UserApi.findPageAdminUser({
|
||||
...adminQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -123,7 +127,28 @@ function AdminPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType,
|
||||
keyword,
|
||||
grpId,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeywordType("");
|
||||
setKeyword("");
|
||||
setGrpId("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType: "",
|
||||
keyword: "",
|
||||
grpId: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteAdmins = async () => {
|
||||
@@ -161,11 +186,7 @@ function AdminPage() {
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-6 p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="mr-2 text-label">관리자구분</Label>
|
||||
<Select
|
||||
onValueChange={(value) =>
|
||||
setAdminQuery({ ...adminQuery, grpId: value })
|
||||
}
|
||||
>
|
||||
<Select value={grpId} onValueChange={(value) => setGrpId(value)}>
|
||||
<SelectTrigger className="h-[3.2rem] w-[16rem] !text-[1.3rem]">
|
||||
<SelectValue placeholder="그룹 선택" />
|
||||
</SelectTrigger>
|
||||
@@ -183,9 +204,8 @@ function AdminPage() {
|
||||
관리자
|
||||
</Label>
|
||||
<Select
|
||||
onValueChange={(value) =>
|
||||
setAdminQuery({ ...adminQuery, keywordType: value })
|
||||
}
|
||||
value={keywordType}
|
||||
onValueChange={(value) => setKeywordType(value)}
|
||||
>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem] !text-[1.3rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
@@ -197,16 +217,22 @@ function AdminPage() {
|
||||
</Select>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={adminQuery.keyword}
|
||||
onChange={(e) =>
|
||||
setAdminQuery({ ...adminQuery, keyword: e.target.value })
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
placeholder="검색어 입력"
|
||||
/>
|
||||
</div>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -26,36 +26,32 @@ type DateRange = {
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
function ApiLogPage({}) {
|
||||
// 1주일 전부터 오늘까지 기본 날짜 설정
|
||||
function ApiLogPage() {
|
||||
const defaultDateRange: DateRange = {
|
||||
from: dayjs().subtract(7, "day").toDate(),
|
||||
to: dayjs().toDate(),
|
||||
};
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const [loginlogPostQuery, setLoginlogPostQuery] = useState<ApiLogParams>({
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
const [userId, setUserId] = useState("");
|
||||
const [apiHttpPmhd, setApiHttpPmhd] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<ApiLogParams>({
|
||||
userId: "",
|
||||
siteId: "",
|
||||
apiHttpPmhd: "",
|
||||
dmndUrl: "",
|
||||
ipAddr: "",
|
||||
dmndDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
dmndDtEnd: dayjs().format("YYYY-MM-DD"),
|
||||
sort: "lgnDt,asc",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.APILOG, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
ApiLogApi.getApiLogList({
|
||||
...loginlogPostQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -81,13 +77,31 @@ function ApiLogPage({}) {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
userId,
|
||||
apiHttpPmhd,
|
||||
dmndDtStart: dayjs(dateRange.from).format("YYYY-MM-DD"),
|
||||
dmndDtEnd: dayjs(dateRange.to).format("YYYY-MM-DD"),
|
||||
});
|
||||
};
|
||||
|
||||
const handleLoginlogParamsChange =
|
||||
(key: keyof ApiLogParams) => (value: unknown) => {
|
||||
setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value });
|
||||
};
|
||||
const handleReset = () => {
|
||||
setUserId("");
|
||||
setApiHttpPmhd("");
|
||||
setDateRange(defaultDateRange);
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
userId: "",
|
||||
apiHttpPmhd: "",
|
||||
dmndDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
dmndDtEnd: dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
};
|
||||
|
||||
const handlePresetDateRange = (value: string) => {
|
||||
let presetDateRange: DateRange | undefined;
|
||||
@@ -113,19 +127,7 @@ function ApiLogPage({}) {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
handleDateRangeChange(presetDateRange);
|
||||
};
|
||||
|
||||
const handleDateRangeChange = (value: DateRange | undefined) => {
|
||||
setDateRange(value || defaultDateRange);
|
||||
setLoginlogPostQuery({
|
||||
...loginlogPostQuery,
|
||||
dmndDtStart:
|
||||
dayjs(value?.from).format("YYYY-MM-DD") ??
|
||||
dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
dmndDtEnd:
|
||||
dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
setDateRange(presetDateRange || defaultDateRange);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -135,7 +137,7 @@ function ApiLogPage({}) {
|
||||
<Label className="mr-2">조회기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={handleDateRangeChange}
|
||||
onValueChange={(value) => setDateRange(value || defaultDateRange)}
|
||||
>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
@@ -152,14 +154,15 @@ function ApiLogPage({}) {
|
||||
</DateRangePicker>
|
||||
<Label className="ml-4 mr-2 text-label">사용자ID</Label>
|
||||
<Input
|
||||
value={userId}
|
||||
onChange={(e) => setUserId(e.target.value)}
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={loginlogPostQuery.userId}
|
||||
onChange={(e) =>
|
||||
handleLoginlogParamsChange("userId")(e.target.value)
|
||||
}
|
||||
/>
|
||||
<Label className="ml-4 mr-2 text-label">상태</Label>
|
||||
<Select onValueChange={handleLoginlogParamsChange("apiHttpPmhd")}>
|
||||
<Select
|
||||
value={apiHttpPmhd}
|
||||
onValueChange={(value) => setApiHttpPmhd(value)}
|
||||
>
|
||||
<SelectTrigger className="w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
@@ -173,12 +176,20 @@ function ApiLogPage({}) {
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h3 className="text-2xl font-medium text-label">로그인로그 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">작업로그 목록</h3>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
value={table.getState().pagination.pageSize.toString()}
|
||||
|
||||
@@ -84,7 +84,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>배너명</TableHead>
|
||||
<TableHead data-required>배너명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -105,7 +105,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>배너구분</TableHead>
|
||||
<TableHead data-required>배너구분</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -210,7 +210,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>링크 열기</TableHead>
|
||||
<TableHead data-required>링크 열기</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -260,7 +260,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용여부</TableHead>
|
||||
<TableHead data-required>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -28,17 +28,21 @@ function BannerPage() {
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
|
||||
const [bannerQuery, setBannerQuery] = useState<BannerParams>({ bnrTtl: "" });
|
||||
const [bnrTtl, setBnrTtl] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<BannerParams>({
|
||||
bnrTtl: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
BannerApi.getBannerList({
|
||||
...bannerQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -46,7 +50,22 @@ function BannerPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
bnrTtl,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setBnrTtl("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
bnrTtl: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Banner) => {
|
||||
@@ -107,14 +126,20 @@ function BannerPage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={bannerQuery.bnrTtl}
|
||||
onChange={(e) =>
|
||||
setBannerQuery({ ...bannerQuery, bnrTtl: e.target.value })
|
||||
}
|
||||
value={bnrTtl}
|
||||
onChange={(e) => setBnrTtl(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -54,7 +54,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>게시판명</TableHead>
|
||||
<TableHead data-required>게시판명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -75,7 +75,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>게시판 설명</TableHead>
|
||||
<TableHead data-required>게시판 설명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -96,7 +96,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>게시판 유형</TableHead>
|
||||
<TableHead data-required>게시판 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -116,7 +116,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>참조 사용 여부</TableHead>
|
||||
<TableHead data-required>참조 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -146,7 +146,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일 사용 여부</TableHead>
|
||||
<TableHead data-required>첨부파일 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -236,7 +236,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>답글 사용 여부</TableHead>
|
||||
<TableHead data-required>답글 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -295,7 +295,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>댓글 사용 여부</TableHead>
|
||||
<TableHead data-required>댓글 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -329,7 +329,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>게시물평가 사용 여부</TableHead>
|
||||
<TableHead data-required>게시물평가 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -359,7 +359,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 사용 여부</TableHead>
|
||||
<TableHead data-required>팝업 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -389,7 +389,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>SMS알림 사용 여부</TableHead>
|
||||
<TableHead data-required>SMS알림 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -419,7 +419,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>상단고정 사용 여부</TableHead>
|
||||
<TableHead data-required>상단고정 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -449,7 +449,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>게시기간 사용 여부</TableHead>
|
||||
<TableHead data-required>게시기간 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -479,7 +479,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>개인정보 표시 여부</TableHead>
|
||||
<TableHead data-required>개인정보 표시 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -508,7 +508,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead data-required>사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -29,9 +29,12 @@ function BbsPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [boardParams, setBoardParams] = useState<BoardParams>({
|
||||
const [bbsTypeCd, setBbsTypeCd] = useState("");
|
||||
const [bbsNm, setBbsNm] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<BoardParams>({
|
||||
bbsTypeCd: "",
|
||||
bbsNm: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -39,10 +42,10 @@ function BbsPage() {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.BBS, pagination],
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.BBS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
BoardApi.getBoardList({
|
||||
...boardParams,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -50,7 +53,25 @@ function BbsPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
bbsTypeCd,
|
||||
bbsNm,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setBbsTypeCd("");
|
||||
setBbsNm("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
bbsTypeCd: "",
|
||||
bbsNm: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Board) => {
|
||||
@@ -86,10 +107,8 @@ function BbsPage() {
|
||||
</Label>
|
||||
<CodeSelect
|
||||
upCd="BBSTYPE"
|
||||
value={boardParams.bbsTypeCd}
|
||||
onValueChange={(value) =>
|
||||
setBoardParams({ ...boardParams, bbsTypeCd: value })
|
||||
}
|
||||
value={bbsTypeCd}
|
||||
onValueChange={(value) => setBbsTypeCd(value)}
|
||||
/>
|
||||
<Label htmlFor="bbsNm" className="ml-4 mr-2 text-label">
|
||||
게시판명
|
||||
@@ -97,14 +116,20 @@ function BbsPage() {
|
||||
<Input
|
||||
id="bbsNm"
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={boardParams.bbsNm}
|
||||
onChange={(e) =>
|
||||
setBoardParams({ ...boardParams, bbsNm: e.target.value })
|
||||
}
|
||||
value={bbsNm}
|
||||
onChange={(e) => setBbsNm(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -5,56 +5,62 @@ import type { LwpoCode } from "@/entities/code";
|
||||
import { LwpoCodeManagementDialog } from "../ui/lwpocode-dialog";
|
||||
import { useState } from "react";
|
||||
|
||||
export const columns: ColumnDef<LwpoCode>[] = [
|
||||
{
|
||||
id: "no",
|
||||
header: "NO",
|
||||
cell: ({ row, table }) => {
|
||||
const totalRows = table.getRowCount();
|
||||
const { pageIndex, pageSize } = table.getState().pagination;
|
||||
return totalRows - (pageIndex * pageSize + row.index);
|
||||
// columns를 함수로 변경하여 onAfterSave를 주입받음
|
||||
export function getColumns(onAfterSave: () => void) : ColumnDef<LwpoCode>[] {
|
||||
return [
|
||||
{
|
||||
id: "no",
|
||||
header: "NO",
|
||||
cell: ({ row, table }) => {
|
||||
const totalRows = table.getRowCount();
|
||||
const { pageIndex, pageSize } = table.getState().pagination;
|
||||
return totalRows - (pageIndex * pageSize + row.index);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "lwpoCd",
|
||||
header: "하위코드",
|
||||
},
|
||||
{
|
||||
accessorKey: "lwpoCdNm",
|
||||
header: "하위코드명",
|
||||
},
|
||||
{
|
||||
accessorKey: "useYn",
|
||||
header: "사용 여부",
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
header: "등록일",
|
||||
cell: ({ row }) => {
|
||||
const date = dayjs(row.original.regDt);
|
||||
return date.format("YYYY-MM-DD");
|
||||
{
|
||||
accessorKey: "lwpoCd",
|
||||
header: "하위코드",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "관리",
|
||||
cell: ({ row }) => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<LwpoCodeManageButton
|
||||
lwpoCd={row.original.lwpoCd}
|
||||
upCd={row.original.upCd}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
{
|
||||
accessorKey: "lwpoCdNm",
|
||||
header: "하위코드명",
|
||||
},
|
||||
{
|
||||
accessorKey: "useYn",
|
||||
header: "사용 여부",
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
header: "등록일",
|
||||
cell: ({ row }) => {
|
||||
const date = dayjs(row.original.regDt);
|
||||
return date.format("YYYY-MM-DD");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "관리",
|
||||
cell: ({ row }) => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<LwpoCodeManageButton
|
||||
lwpoCd={row.original.lwpoCd}
|
||||
upCd={row.original.upCd}
|
||||
onAfterSave={onAfterSave}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const LwpoCodeManageButton = ({
|
||||
lwpoCd,
|
||||
upCd,
|
||||
onAfterSave,
|
||||
}: {
|
||||
lwpoCd: string;
|
||||
upCd: string;
|
||||
onAfterSave: () => void;
|
||||
}) => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
return (
|
||||
@@ -63,6 +69,7 @@ const LwpoCodeManageButton = ({
|
||||
upCd={upCd}
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
onAfterSave={onAfterSave}
|
||||
>
|
||||
<Button size="sm">관리</Button>
|
||||
</LwpoCodeManagementDialog>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { memo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Button,
|
||||
@@ -38,6 +38,7 @@ type ManagementLwpoCodeDialogProps = {
|
||||
children?: React.ReactNode;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onAfterSave?: () => void; // 콜백 추가
|
||||
};
|
||||
|
||||
const LwpoCodeManagementDialog = memo(
|
||||
@@ -47,17 +48,18 @@ const LwpoCodeManagementDialog = memo(
|
||||
children,
|
||||
open,
|
||||
onOpenChange,
|
||||
onAfterSave, // 콜백 사용
|
||||
...props
|
||||
}: ManagementLwpoCodeDialogProps) => {
|
||||
const isRegistration = !lwpoCd;
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const { data: detailData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, lwpoCd],
|
||||
queryFn: () => LwpoCodeApi.lwpoCodeDetail(upCd!, lwpoCd!),
|
||||
enabled: !!upCd && !isRegistration,
|
||||
enabled: !!upCd && !isRegistration && open,
|
||||
});
|
||||
|
||||
const { data: upCodeData } = useQuery({
|
||||
@@ -69,7 +71,13 @@ const LwpoCodeManagementDialog = memo(
|
||||
const { mutateAsync: createLwpoCode } = useMutationWrapper({
|
||||
mutationFn: (data: LwpoCodeFormData) => LwpoCodeApi.createLwpoCode(data),
|
||||
onSuccess: () => {
|
||||
alert("등록되었습니다.", () => onOpenChange?.(false));
|
||||
alert("등록되었습니다.", () => {
|
||||
onOpenChange?.(false);
|
||||
onAfterSave?.();
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -77,7 +85,10 @@ const LwpoCodeManagementDialog = memo(
|
||||
mutationFn: (data: LwpoCodeFormData) =>
|
||||
LwpoCodeApi.updateLwpoCode(upCd!, lwpoCd!, data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.", () => onOpenChange?.(false));
|
||||
alert("수정되었습니다.", () => {
|
||||
onOpenChange?.(false);
|
||||
onAfterSave?.();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -96,15 +107,38 @@ const LwpoCodeManagementDialog = memo(
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit =
|
||||
(callback: (data: LwpoCodeFormData) => void) =>
|
||||
async (data: LwpoCodeFormData) => {
|
||||
const confirmMessage = isRegistration
|
||||
? "등록하시겠습니까?"
|
||||
: "수정하시겠습니까?";
|
||||
|
||||
if (await confirm(confirmMessage)) callback(data);
|
||||
};
|
||||
const handleRegister = async () => {
|
||||
if (!upCd) {
|
||||
alert("상위코드가 없습니다.");
|
||||
return;
|
||||
}
|
||||
const confirmMessage = "등록하시겠습니까?";
|
||||
if (await confirm(confirmMessage)) {
|
||||
const rawValues = form.getValues();
|
||||
const values = {
|
||||
...rawValues,
|
||||
upCd: upCd,
|
||||
sortSeq: Number(rawValues.sortSeq) || 0,
|
||||
};
|
||||
await createLwpoCode(values);
|
||||
}
|
||||
};
|
||||
const handleSave = async () => {
|
||||
if (!upCd) {
|
||||
alert("상위코드가 없습니다.");
|
||||
return;
|
||||
}
|
||||
const confirmMessage = "수정하시겠습니까?";
|
||||
if (await confirm(confirmMessage)) {
|
||||
const rawValues = form.getValues();
|
||||
const values = {
|
||||
...rawValues,
|
||||
upCd: upCd,
|
||||
sortSeq: Number(rawValues.sortSeq) || 0,
|
||||
};
|
||||
await updateLwpoCode(values);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange} {...props}>
|
||||
@@ -117,9 +151,14 @@ const LwpoCodeManagementDialog = memo(
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(
|
||||
onSubmit(isRegistration ? createLwpoCode : updateLwpoCode),
|
||||
)}
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (isRegistration) {
|
||||
handleRegister();
|
||||
} else {
|
||||
handleSave();
|
||||
}
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<Table variant="secondary">
|
||||
@@ -251,6 +290,9 @@ const LwpoCodeManagementDialog = memo(
|
||||
{...field}
|
||||
type="number"
|
||||
value={field.value ?? 0}
|
||||
onChange={(e) => {
|
||||
field.onChange(parseInt(e.target.value));
|
||||
}}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="정렬순서를 입력해주세요"
|
||||
/>
|
||||
@@ -292,7 +334,11 @@ const LwpoCodeManagementDialog = memo(
|
||||
</TableBody>
|
||||
</Table>
|
||||
<DialogFooter className="!justify-center">
|
||||
<Button size="lg" type="submit">
|
||||
<Button
|
||||
size="lg"
|
||||
type="submit"
|
||||
onClick={isRegistration ? handleRegister : handleSave}
|
||||
>
|
||||
{isRegistration ? "등록" : "저장"}
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Button, Input, Label } from "@common/components";
|
||||
import { LwpoCodeApi, type LwpoCodeParams } from "@/entities/code";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useDataTable } from "@/shared/lib";
|
||||
import { columns } from "../model/lwpocode-table-columns";
|
||||
import { getColumns } from "../model/lwpocode-table-columns";
|
||||
import { LwpoCodeManagementDialog } from "./lwpocode-dialog";
|
||||
|
||||
type LwpoCodeSectionProps = {
|
||||
@@ -15,8 +15,10 @@ type LwpoCodeSectionProps = {
|
||||
function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [lwpoCodeQuery, setLwpoCodeQuery] = useState<LwpoCodeParams>({
|
||||
const [lwpoCdNm, setLwpoCdNm] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<LwpoCodeParams>({
|
||||
lwpoCdNm: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -24,10 +26,10 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
LwpoCodeApi.getLwpoCodeList({
|
||||
...lwpoCodeQuery,
|
||||
...searchParams,
|
||||
upCd,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
@@ -36,6 +38,12 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
// 등록/수정 후 목록 재조회 콜백
|
||||
const handleAfterSave = () => {
|
||||
query.refetch();
|
||||
};
|
||||
const columns = getColumns(handleAfterSave);
|
||||
|
||||
const { mutateAsync: codeRefreshAll } = useMutation({
|
||||
mutationFn: LwpoCodeApi.codeRefreshAll,
|
||||
onSuccess: () => {
|
||||
@@ -59,7 +67,22 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
lwpoCdNm,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setLwpoCdNm("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
lwpoCdNm: "",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,17 +98,20 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={lwpoCodeQuery.lwpoCdNm}
|
||||
onChange={(e) =>
|
||||
setLwpoCodeQuery({
|
||||
...lwpoCodeQuery,
|
||||
lwpoCdNm: e.target.value,
|
||||
})
|
||||
}
|
||||
value={lwpoCdNm}
|
||||
onChange={(e) => setLwpoCdNm(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
@@ -96,8 +122,10 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
|
||||
코드갱신
|
||||
</Button>
|
||||
<LwpoCodeManagementDialog
|
||||
upCd={upCd}
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
onAfterSave={handleAfterSave}
|
||||
>
|
||||
<Button>등록</Button>
|
||||
</LwpoCodeManagementDialog>
|
||||
|
||||
@@ -14,8 +14,10 @@ type UpCodeSectionProps = {
|
||||
|
||||
function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [upCodeQuery, setUpCodeQuery] = useState<UpCodeParams>({
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<UpCodeParams>({
|
||||
keyword: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -23,10 +25,10 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.UP_CODE, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.UP_CODE, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
UpCodeApi.getUpCodeList({
|
||||
...upCodeQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -40,7 +42,22 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
};
|
||||
|
||||
const { tableComponent, tableInstance: table } = useDataTable({
|
||||
@@ -66,17 +83,20 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={upCodeQuery.keyword}
|
||||
onChange={(e) =>
|
||||
setUpCodeQuery({
|
||||
...upCodeQuery,
|
||||
keyword: e.target.value,
|
||||
})
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -60,7 +60,7 @@ function ContentsForm({
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>콘텐츠 제목</TableHead>
|
||||
<TableHead data-required>콘텐츠 제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -82,7 +82,7 @@ function ContentsForm({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>콘텐츠 내용</TableHead>
|
||||
<TableHead data-required>콘텐츠 내용</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -228,7 +228,7 @@ function ContentsForm({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용여부</TableHead>
|
||||
<TableHead data-required>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -17,19 +17,21 @@ import { columns } from "../model/table-columns";
|
||||
function ContentsPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [contentsQuery, setContentsQuery] = useState<ContentsParams>({
|
||||
const [contsTtl, setContsTtl] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<ContentsParams>({
|
||||
contsTtl: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
ContentsApi.getContentsList({
|
||||
...contentsQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -38,7 +40,22 @@ function ContentsPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
contsTtl,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setContsTtl("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
contsTtl: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Contents) => {
|
||||
@@ -72,12 +89,20 @@ function ContentsPage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={contentsQuery.contsTtl}
|
||||
onChange={(e) => setContentsQuery({ contsTtl: e.target.value })}
|
||||
value={contsTtl}
|
||||
onChange={(e) => setContsTtl(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -97,6 +97,16 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: rejectContents } = useMutationWrapper({
|
||||
mutationFn: () => ContentsApi.rejectContents(contsSn, contsVer),
|
||||
onSuccess: () => {
|
||||
alert("반려되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<ContentsFormData>({
|
||||
resolver: zodResolver(contentsFormSchema),
|
||||
values: contentsDetail,
|
||||
@@ -107,14 +117,20 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
||||
(contentsVersionList?.content?.[0]?.contsVer ?? 0) > 1;
|
||||
|
||||
const onRequestApprove = async () => {
|
||||
const message =
|
||||
"승인 요청하면 더이상 내용을 수정할 수 없습니다.\n요청하시겠습니까?";
|
||||
if (await confirm(message)) requestApprove();
|
||||
if (
|
||||
await confirm(
|
||||
"승인 요청하면 더이상 내용을 수정할 수 없습니다.\n요청하시겠습니까?",
|
||||
)
|
||||
)
|
||||
requestApprove();
|
||||
};
|
||||
|
||||
const onApprove = async () => {
|
||||
const message = "승인하시겠습니까?";
|
||||
if (await confirm(message)) approveContents();
|
||||
if (await confirm("승인하시겠습니까?")) approveContents();
|
||||
};
|
||||
|
||||
const onReject = async () => {
|
||||
if (await confirm("반려하시겠습니까?")) rejectContents();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -158,7 +174,9 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
||||
{approveStatus === CONTENTS_APPROVE_STATUS.REQUESTED && (
|
||||
<>
|
||||
<Button onClick={onApprove}>승인</Button>
|
||||
<Button color="gray">반려</Button>
|
||||
<Button color="gray" onClick={onReject}>
|
||||
반려
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,10 @@ function FilePage() {
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [attachmentQuery, setAttachmentQuery] = useState<AttachmentParams>({
|
||||
const [orgnlFileNm, setOrgnlFileNm] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<AttachmentParams>({
|
||||
orgnlFileNm: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -23,10 +25,10 @@ function FilePage() {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, pagination],
|
||||
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
AttachmentApi.getAttachmentsList({
|
||||
...attachmentQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -64,14 +66,24 @@ function FilePage() {
|
||||
},
|
||||
});
|
||||
|
||||
const handleAttachmentQueryChange =
|
||||
(key: keyof AttachmentParams) => (value: string) => {
|
||||
setAttachmentQuery({ ...attachmentQuery, [key]: value });
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
orgnlFileNm,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setOrgnlFileNm("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
orgnlFileNm: "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -86,10 +98,8 @@ function FilePage() {
|
||||
<Input
|
||||
id="keyword"
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={attachmentQuery.orgnlFileNm || ""}
|
||||
onChange={(e) =>
|
||||
handleAttachmentQueryChange("orgnlFileNm")(e.target.value)
|
||||
}
|
||||
value={orgnlFileNm}
|
||||
onChange={(e) => setOrgnlFileNm(e.target.value)}
|
||||
placeholder="파일명을 입력하세요"
|
||||
/>
|
||||
</div>
|
||||
@@ -97,6 +107,14 @@ function FilePage() {
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -4,7 +4,11 @@ import { type GroupListResponseDto } from "@/entities/group";
|
||||
import { MenuAuthorityDialog } from "../ui/menu-authority-dialog";
|
||||
import { GroupsDialog } from "../ui/groups-dialog";
|
||||
|
||||
export const columns: ColumnDef<GroupListResponseDto>[] = [
|
||||
export const createColumns = ({
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete: (groupId: string) => void;
|
||||
}): ColumnDef<GroupListResponseDto>[] => [
|
||||
{
|
||||
accessorKey: "groupId",
|
||||
header: "그룹코드",
|
||||
@@ -20,18 +24,28 @@ export const columns: ColumnDef<GroupListResponseDto>[] = [
|
||||
{
|
||||
id: "actions",
|
||||
header: "관리",
|
||||
cell: ({ row }) => (
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex justify-center gap-2"
|
||||
>
|
||||
<MenuAuthorityDialog groupId={row.original.groupId} />
|
||||
<GroupsDialog groupId={row.original.groupId}>
|
||||
<Button size="sm" color="white">
|
||||
수정
|
||||
cell: ({ row }) => {
|
||||
const handleDelete = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onDelete(row.original.groupId);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="flex justify-center gap-2"
|
||||
>
|
||||
<MenuAuthorityDialog groupId={row.original.groupId} />
|
||||
<GroupsDialog groupId={row.original.groupId}>
|
||||
<Button size="sm" color="white">
|
||||
수정
|
||||
</Button>
|
||||
</GroupsDialog>
|
||||
<Button size="sm" color="red" onClick={handleDelete}>
|
||||
삭제
|
||||
</Button>
|
||||
</GroupsDialog>
|
||||
</div>
|
||||
),
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -37,7 +37,7 @@ function GroupForm({ form }: GroupFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>그룹코드</TableHead>
|
||||
<TableHead data-required>그룹코드</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -59,7 +59,7 @@ function GroupForm({ form }: GroupFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>그룹명</TableHead>
|
||||
<TableHead data-required>그룹명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -81,7 +81,7 @@ function GroupForm({ form }: GroupFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>그룹구분</TableHead>
|
||||
<TableHead data-required>그룹구분</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -146,7 +146,7 @@ function GroupForm({ form }: GroupFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>관리권한</TableHead>
|
||||
<TableHead data-required>관리권한</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -176,7 +176,7 @@ function GroupForm({ form }: GroupFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>권한</TableHead>
|
||||
<TableHead data-required>권한</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type RowSelectionState,
|
||||
} from "@tanstack/react-table";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { Button } from "@common/components";
|
||||
import { Button,Input, Label } from "@common/components";
|
||||
import { GroupUsersApi } from "@/entities/group-user";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useDataTable } from "@/shared/lib";
|
||||
@@ -32,13 +32,17 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const [userNm, setUserNm] = useState("");
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination, userNm],
|
||||
queryFn: () =>
|
||||
GroupUsersApi.findPageGroupUser({
|
||||
keyword: groupId,
|
||||
userNm: userNm,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -46,6 +50,20 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
// userNm이 이미 state에 반영되어 있으므로 별도 처리 불필요
|
||||
// 쿼리키에 userNm이 포함되어 있어 자동 refetch됨
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setUserNm("");
|
||||
};
|
||||
|
||||
const { tableComponent, tableInstance: table } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
@@ -97,6 +115,29 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
|
||||
|
||||
return (
|
||||
<div className="flex-1">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label htmlFor="" className="mr-2 text-label">
|
||||
이름
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={userNm}
|
||||
onChange={(e) => setUserNm(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">그룹 사용자 목록</h3>
|
||||
|
||||
@@ -43,7 +43,7 @@ const GroupsDialog = memo(
|
||||
const { data: group } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, groupId],
|
||||
queryFn: () => GroupApi.findGroupById(groupId!),
|
||||
enabled: !!groupId,
|
||||
enabled: !!groupId && open, // open 시점 groupId가 있을 때만 상세 조회 API 호출
|
||||
});
|
||||
|
||||
const { mutateAsync: createGroup } = useMutationWrapper({
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
keepPreviousData,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import { Button } from "@common/components";
|
||||
import { GroupApi } from "@/entities/group";
|
||||
import { Button, Input, Label } from "@common/components";
|
||||
import { GroupApi , type GroupSearchParams} from "@/entities/group";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { columns } from "../model/groups-table-columns";
|
||||
import { createColumns } from "../model/groups-table-columns";
|
||||
import { GroupsDialog } from "./groups-dialog";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
|
||||
type GroupsSectionProps = {
|
||||
handleSelectedGroup: (groupId: string) => void;
|
||||
@@ -20,25 +26,73 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) {
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const [groupNm, setGroupNm] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<GroupSearchParams>({
|
||||
groupNm: "",
|
||||
delYn :"N",
|
||||
sort : "groupId,desc",
|
||||
});
|
||||
|
||||
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS,searchParams, pagination],
|
||||
queryFn: () =>
|
||||
GroupApi.findPageGroup({
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: deleteGroup } = useMutationWrapper({
|
||||
mutationFn: (groupId: string) => GroupApi.deleteGroup(groupId),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) => query.queryKey[0] === ENDPOINT.CMS_SERVICE.GROUPS,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleDelete = async (groupId: string) => {
|
||||
if (await confirm("삭제하시겠습니까?")) {
|
||||
await deleteGroup(groupId);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
groupNm,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setGroupNm("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
groupNm: "",
|
||||
delYn:"N"
|
||||
});
|
||||
};
|
||||
const handleRowClick = (row: { groupId: string }) => {
|
||||
table.setRowSelection({ [row.groupId]: true });
|
||||
handleSelectedGroup(row.groupId);
|
||||
handleSelectedGroup(row.groupId); // 1. 리스트에서 선택된 groupId 저장
|
||||
};
|
||||
|
||||
const { tableComponent, tableInstance: table } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
columns: createColumns({ onDelete: handleDelete }),
|
||||
state: { pagination },
|
||||
manualPagination: true,
|
||||
enableMultiRowSelection: false,
|
||||
@@ -54,6 +108,43 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) {
|
||||
|
||||
return (
|
||||
<div className="flex-1">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label htmlFor="" className="mr-2 text-label">
|
||||
그룹명
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={groupNm}
|
||||
onChange={(e) => setGroupNm(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
<Label htmlFor="delYn" className="ml-4 mr-2 text-label">
|
||||
삭제여부
|
||||
</Label>
|
||||
<input
|
||||
id="delYn"
|
||||
type="checkbox"
|
||||
checked={searchParams.delYn === "Y"}
|
||||
onChange={(e) =>
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
delYn: e.target.checked ? "Y" : "N",
|
||||
})
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">그룹 목록</h3>
|
||||
|
||||
@@ -42,8 +42,9 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
file: uploadFile,
|
||||
response: uploadResponse,
|
||||
progress,
|
||||
isValid,
|
||||
setFile,
|
||||
} = useTus();
|
||||
} = useTus({ accept: ".jpeg,.jpg,.png" });
|
||||
const setAttachmentFileCode = useAttachmentStore(
|
||||
(state) => state.setAttachmentFileCode,
|
||||
);
|
||||
@@ -90,7 +91,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>이미지명</TableHead>
|
||||
<TableHead data-required>이미지명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -111,7 +112,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableHead data-required>정렬순서</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -137,7 +138,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>이미지유형</TableHead>
|
||||
<TableHead data-required>이미지유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -157,7 +158,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead data-required>첨부파일</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -208,24 +209,26 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
customPreview={
|
||||
<>
|
||||
<Label htmlFor="atflCd">
|
||||
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
|
||||
<div className="relative h-full w-full">
|
||||
{imageUrl && (
|
||||
<Image
|
||||
src={imageUrl}
|
||||
className="object-contain"
|
||||
fill
|
||||
alt="업로드된 이미지"
|
||||
<div className="h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
|
||||
{isValid && (
|
||||
<div className="relative size-full">
|
||||
{imageUrl && (
|
||||
<Image
|
||||
src={imageUrl}
|
||||
className="object-contain"
|
||||
fill
|
||||
alt="업로드된 이미지"
|
||||
/>
|
||||
)}
|
||||
<p className="absolute bottom-3 left-0 z-10 p-2 text-base leading-none">
|
||||
{uploadFile?.name}
|
||||
</p>
|
||||
<Progress
|
||||
value={progress}
|
||||
className="absolute bottom-0 left-0 z-10"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="absolute bottom-3 left-0 z-10 p-2 text-base leading-none">
|
||||
{uploadFile?.name}
|
||||
</p>
|
||||
<Progress
|
||||
value={progress}
|
||||
className="absolute bottom-0 left-0 z-10"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Label>
|
||||
<div className="mb-2 text-lg font-medium text-label">
|
||||
@@ -269,7 +272,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableHead>사용여부</TableHead>
|
||||
<TableHead data-required>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -24,22 +24,23 @@ type PostingTarget = {
|
||||
function ImagePage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
|
||||
const [imageQuery, setImageQuery] = useState<ImageParams>({
|
||||
keyword: "" ,
|
||||
sort:"regDt,desc"
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<ImageParams>({
|
||||
keyword: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.IMAGES, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.IMAGES, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
ImageApi.getImageList({
|
||||
...imageQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -47,7 +48,22 @@ function ImagePage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Image) => {
|
||||
@@ -99,14 +115,20 @@ function ImagePage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={imageQuery.keyword}
|
||||
onChange={(e) =>
|
||||
setImageQuery({ ...imageQuery, keyword: e.target.value })
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -27,8 +27,7 @@ type DateRange = {
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
function LoginlogPage({}) {
|
||||
// 1주일 전부터 오늘까지 기본 날짜 설정
|
||||
function LoginlogPage() {
|
||||
const defaultDateRange: DateRange = {
|
||||
from: dayjs().subtract(7, "day").toDate(),
|
||||
to: dayjs().toDate(),
|
||||
@@ -41,24 +40,21 @@ function LoginlogPage({}) {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const [loginlogPostQuery, setLoginlogPostQuery] = useState<LoginlogParams>({
|
||||
const [userId, setUserId] = useState("");
|
||||
const [errOcrnYn, setErrOcrnYn] = useState("N");
|
||||
const [searchParams, setSearchParams] = useState<LoginlogParams>({
|
||||
userId: "",
|
||||
siteId: "",
|
||||
errOcrnYn: "N",
|
||||
remoteIp: "",
|
||||
lgnMthd: "",
|
||||
lgnErrCd: "",
|
||||
searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
searchDateTo: dayjs().format("YYYY-MM-DD"),
|
||||
sort: "lgnDt,desc",
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
LoginlogApi.getLoginlogList({
|
||||
...loginlogPostQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -85,15 +81,34 @@ function LoginlogPage({}) {
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsManualLoading(true);
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
userId,
|
||||
errOcrnYn,
|
||||
searchDateFrom: dayjs(dateRange.from).format("YYYY-MM-DD"),
|
||||
searchDateTo: dayjs(dateRange.to).format("YYYY-MM-DD"),
|
||||
});
|
||||
query.refetch().finally(() => {
|
||||
setIsManualLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleLoginlogParamsChange =
|
||||
(key: keyof LoginlogParams) => (value: unknown) => {
|
||||
setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value });
|
||||
};
|
||||
const handleReset = () => {
|
||||
setUserId("");
|
||||
setErrOcrnYn("N");
|
||||
setDateRange(defaultDateRange);
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
userId: "",
|
||||
errOcrnYn: "N",
|
||||
searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
searchDateTo: dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
};
|
||||
|
||||
const handlePresetDateRange = (value: string) => {
|
||||
let presetDateRange: DateRange | undefined;
|
||||
@@ -119,19 +134,7 @@ function LoginlogPage({}) {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
handleDateRangeChange(presetDateRange);
|
||||
};
|
||||
|
||||
const handleDateRangeChange = (value: DateRange | undefined) => {
|
||||
setDateRange(value || defaultDateRange);
|
||||
setLoginlogPostQuery({
|
||||
...loginlogPostQuery,
|
||||
searchDateFrom:
|
||||
dayjs(value?.from).format("YYYY-MM-DD") ??
|
||||
dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
searchDateTo:
|
||||
dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
setDateRange(presetDateRange || defaultDateRange);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -141,7 +144,7 @@ function LoginlogPage({}) {
|
||||
<Label className="mr-2">조회기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={handleDateRangeChange}
|
||||
onValueChange={(value) => setDateRange(value || defaultDateRange)}
|
||||
>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
@@ -159,13 +162,14 @@ function LoginlogPage({}) {
|
||||
<Label className="ml-4 mr-2 text-label">사용자ID</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={loginlogPostQuery.userId}
|
||||
onChange={(e) =>
|
||||
handleLoginlogParamsChange("userId")(e.target.value)
|
||||
}
|
||||
value={userId}
|
||||
onChange={(e) => setUserId(e.target.value)}
|
||||
/>
|
||||
<Label className="ml-4 mr-2 text-label">상태</Label>
|
||||
<Select onValueChange={handleLoginlogParamsChange("errOcrnYn")}>
|
||||
<Select
|
||||
value={errOcrnYn}
|
||||
onValueChange={(value) => setErrOcrnYn(value)}
|
||||
>
|
||||
<SelectTrigger className="w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
@@ -183,6 +187,14 @@ function LoginlogPage({}) {
|
||||
? "조회 중..."
|
||||
: "조회"}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageableList } from "@/shared/model";
|
||||
import type {
|
||||
MenuHistory,
|
||||
MenuHistorySearchParams,
|
||||
MenuHistoryService,
|
||||
} from "../model/menu-history-interface";
|
||||
|
||||
export class MenuHistoryApiService implements MenuHistoryService {
|
||||
public async getMenuHistoryList(
|
||||
params: MenuHistorySearchParams,
|
||||
): Promise<PageableList<MenuHistory>> {
|
||||
const response = await client.get<PageableList<MenuHistory>>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENU_HISTORY}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async rollbackMenu(hstrySn: number): Promise<void> {
|
||||
await client.post(`${ENDPOINT.CMS_SERVICE.MENU_HISTORY}/rollback`, {
|
||||
hstrySn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuHistoryApi = new MenuHistoryApiService();
|
||||
@@ -1,65 +0,0 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type {
|
||||
Menu,
|
||||
MenuFormData,
|
||||
MenuService,
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
} from "../model/menu-interface";
|
||||
|
||||
export class MenuApiService implements MenuService {
|
||||
public async getMenuInfo(menuCd: number): Promise<Menu> {
|
||||
const response = await client.get<Menu>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createMenu(data: MenuFormData): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenu(
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteMenu(menuCd: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`);
|
||||
}
|
||||
|
||||
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
|
||||
const response = await client.get<TreeMenu[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenuName(menuCd: number, name: string): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuApi = new MenuApiService();
|
||||
@@ -0,0 +1,33 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type {
|
||||
TreeMenuService,
|
||||
TreeMenu,
|
||||
TreeNode,
|
||||
} from "../model/tree-menu-interface";
|
||||
|
||||
export class TreeMenuApiService implements TreeMenuService {
|
||||
public async getTreeMenuList(siteId: string): Promise<TreeMenu[]> {
|
||||
const response = await client.get<TreeMenu[]>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateMenuName(menuCd: number, name: string): Promise<void> {
|
||||
const response = await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const TreeMenuApi = new TreeMenuApiService();
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
TreeUserOrganization,
|
||||
TreeUser,
|
||||
} from "@/entities/user-organization";
|
||||
import type { TreeMenu, TreeNode } from "../model/menu-interface";
|
||||
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
|
||||
|
||||
export const generateMenuTreeData = (
|
||||
data: TreeMenu[],
|
||||
@@ -153,6 +153,7 @@ export const filterUserOrganizationTreeDataByKeyword = (
|
||||
): NodeModel<TreeUserOrganization | TreeUser>[] => {
|
||||
return Array.from(
|
||||
treeData.reduce((matchedNodes, node) => {
|
||||
if (node.data && "ognzSn" in node.data) return matchedNodes; // 조직 노드는 필터링 제외
|
||||
if (!node.text.includes(keyword)) return matchedNodes;
|
||||
|
||||
matchedNodes.add(node);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Checkbox } from "@common/components";
|
||||
import type { MenuHistory } from "./menu-history-interface";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const columns: ColumnDef<MenuHistory>[] = [
|
||||
{
|
||||
id: "select",
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="행 선택"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "chgTypeNm",
|
||||
header: "이력구분",
|
||||
meta: {
|
||||
width: 80,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "menuKornNm",
|
||||
header: "메뉴명",
|
||||
meta: {
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "bchgData",
|
||||
header: "이전 데이터",
|
||||
cell: ({ row }) => {
|
||||
const parsed = JSON.parse(row.original.bchgData);
|
||||
const stringified = JSON.stringify(parsed, null, 2);
|
||||
return <pre className="break-all text-left">{stringified}</pre>;
|
||||
},
|
||||
meta: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "achgData",
|
||||
header: "변경 데이터",
|
||||
cell: ({ row }) => {
|
||||
const parsed = JSON.parse(row.original.achgData);
|
||||
const stringified = JSON.stringify(parsed, null, 2);
|
||||
return <pre className="break-all text-left">{stringified}</pre>;
|
||||
},
|
||||
meta: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
header: "변경일시",
|
||||
cell: ({ row }) => {
|
||||
return dayjs(row.original.regDt).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "rgtrNm",
|
||||
header: "변경자",
|
||||
meta: {
|
||||
width: 80,
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { PageableParams, PageableList } from "@/shared/model";
|
||||
|
||||
export interface MenuHistorySearchParams extends PageableParams {
|
||||
keyword?: string;
|
||||
keywordType?: string;
|
||||
menuCd?: number;
|
||||
chgTypeCd?: string;
|
||||
startDt?: string;
|
||||
endDt?: string;
|
||||
}
|
||||
|
||||
export interface MenuHistory {
|
||||
hstrySn: number;
|
||||
menuCd: number;
|
||||
chgTypeCd: string;
|
||||
chgTypeNm: string;
|
||||
bchgData: string;
|
||||
achgData: string;
|
||||
rgtr: string;
|
||||
regDt: string;
|
||||
rgtrNm: string;
|
||||
menuKornNm: string;
|
||||
}
|
||||
|
||||
export interface MenuHistoryService {
|
||||
getMenuHistoryList: (
|
||||
params: MenuHistorySearchParams,
|
||||
) => Promise<PageableList<MenuHistory>>;
|
||||
rollbackMenu: (hstrySn: number) => Promise<void>;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MENU_TYPE = {
|
||||
EMPTY: "EMPTY",
|
||||
CONTENTS: "CONTENTS",
|
||||
BOARD: "BOARD",
|
||||
INSIDE: "INSIDE",
|
||||
OUTSIDE: "OUTSIDE",
|
||||
BOTTOM: "BOTTOM",
|
||||
} as const;
|
||||
|
||||
export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE];
|
||||
|
||||
export interface TreeNode {
|
||||
menuCd?: number;
|
||||
name?: string;
|
||||
sortSeq?: number;
|
||||
parentId?: number | null;
|
||||
levelNo?: number;
|
||||
icon?: string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
|
||||
menuKornNm: string;
|
||||
menuEngNm?: string;
|
||||
iconNm?: string;
|
||||
children?: TreeMenu[];
|
||||
urlPath?: string;
|
||||
menuLnkgTypeCd: string;
|
||||
menuLnkgSn?: number;
|
||||
qrcd?: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
requiredUrlPath?: boolean;
|
||||
}
|
||||
|
||||
export const menuFormSchema = z.object({
|
||||
siteId: z.string(),
|
||||
menuKornNm: z.string(),
|
||||
menuSubKornNm: z.string().nullish(),
|
||||
menuEngNm: z.string().nullish(),
|
||||
menuSubEngNm: z.string().nullish(),
|
||||
levelNo: z.number(),
|
||||
urlPath: z.string().nullish(),
|
||||
iconNm: z.string().nullish(),
|
||||
menuLnkgTypeCd: z.enum([
|
||||
MENU_TYPE.EMPTY,
|
||||
MENU_TYPE.CONTENTS,
|
||||
MENU_TYPE.BOARD,
|
||||
MENU_TYPE.INSIDE,
|
||||
MENU_TYPE.OUTSIDE,
|
||||
MENU_TYPE.BOTTOM,
|
||||
]),
|
||||
menuLnkgSn: z.number().nullish(),
|
||||
otptYn: z.string().default("Y"),
|
||||
prntMenuCd: z.number().nullish(),
|
||||
sortSeq: z.number(),
|
||||
evlUseYn: z.string().default("Y"),
|
||||
qrcd: z.string().nullish(),
|
||||
useYn: z.string().default("Y"),
|
||||
newWndwYn: z.string().nullish(),
|
||||
menuLnkgSnNm: z.string().nullish(),
|
||||
menuPicUserUnqId: z.string().nullish(),
|
||||
menuPicUserUnqIdNm: z.string().nullish(),
|
||||
menuPicDsgnYmd: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type MenuFormData = z.infer<typeof menuFormSchema>;
|
||||
export type Menu = Omit<MenuFormData, "iconNm"> & {
|
||||
menuCd: number;
|
||||
iconName?: string;
|
||||
};
|
||||
|
||||
export interface MenuService {
|
||||
getMenuInfo: (menuCd: number) => Promise<Menu>;
|
||||
createMenu: (data: MenuFormData) => Promise<void>;
|
||||
updateMenu: (
|
||||
menuCd: number,
|
||||
data: Omit<MenuFormData, "siteId">,
|
||||
) => Promise<void>;
|
||||
deleteMenu: (menuCd: number) => Promise<void>;
|
||||
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
|
||||
updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
|
||||
updateMenuName: (menuCd: number, name: string) => Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export interface TreeNode {
|
||||
menuCd?: number;
|
||||
name?: string;
|
||||
sortSeq?: number;
|
||||
parentId?: number | null;
|
||||
levelNo?: number;
|
||||
icon?: string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
|
||||
menuKornNm: string;
|
||||
menuEngNm?: string;
|
||||
iconNm?: string;
|
||||
children?: TreeMenu[];
|
||||
urlPath?: string;
|
||||
menuLnkgTypeCd: string;
|
||||
menuLnkgSn?: number;
|
||||
qrcd?: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
requiredUrlPath?: boolean;
|
||||
}
|
||||
|
||||
export interface TreeMenuService {
|
||||
getTreeMenuList: (siteId: string) => Promise<TreeMenu[]>;
|
||||
updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise<void>;
|
||||
updateMenuName: (menuCd: number, name: string) => Promise<void>;
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { MENU_TYPE, type MenuFormData } from "@/entities/menu";
|
||||
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
|
||||
import { MENU_TYPE, type MenuFormData } from "../model/menu-interface";
|
||||
import { InfoTooltip } from "@/shared/ui/info-tooltip";
|
||||
|
||||
interface BaseMenuFormProps {
|
||||
@@ -85,7 +85,7 @@ function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>메뉴 유형</TableHead>
|
||||
<TableHead data-required>메뉴 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { Button } from "@common/components";
|
||||
import { Form } from "@/shared/ui/form";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
LNB,
|
||||
} from "@common/components";
|
||||
import { MenuApi, menuFormSchema, type MenuFormData } from "@/entities/menu";
|
||||
import { ManagementAuthorityDialog } from "@/entities/menu-authority";
|
||||
import type { MenuFormData } from "../model/menu-interface";
|
||||
import { Form } from "@/shared/ui/form";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { BaseMenuForm } from "./base-menu-form";
|
||||
import { TypeMenuForm } from "./type-menu-form";
|
||||
import { MenuHistoryDialog } from "./menu-history-dialog";
|
||||
|
||||
interface MenuFormProps {
|
||||
form: UseFormReturn<MenuFormData>;
|
||||
@@ -29,8 +41,22 @@ function MenuForm({
|
||||
handleDelete,
|
||||
}: MenuFormProps) {
|
||||
const isModifyMode = !!menuCd;
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const { data: menuTemporarilyStoredData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_TEMPORARY_STORAGE, menuCd],
|
||||
queryFn: () => MenuApi.getMenuTemporarilyStoredData(menuCd!),
|
||||
enabled: !!menuCd,
|
||||
});
|
||||
|
||||
const { mutate: menuTemporaryStorage } = useMutationWrapper({
|
||||
mutationFn: () => MenuApi.menuTemporaryStorage(menuCd!, form.getValues()),
|
||||
onSuccess: () => {
|
||||
alert("메뉴 임시 저장이 완료되었습니다.");
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (data: MenuFormData) => {
|
||||
if (isModifyMode) {
|
||||
@@ -58,36 +84,102 @@ function MenuForm({
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuTemporaryStorage = () => {
|
||||
if (!menuCd || !menuFormSchema.parse(form.getValues())) return;
|
||||
menuTemporaryStorage();
|
||||
};
|
||||
|
||||
const handleLoadTemporaryData = () => {
|
||||
if (!menuTemporarilyStoredData?.[0]?.menuTmprStrg) {
|
||||
alert("불러올 임시 저장 데이터가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const storedData = menuTemporarilyStoredData[0].menuTmprStrg;
|
||||
const parsedData = JSON.parse(storedData);
|
||||
form.reset(parsedData);
|
||||
};
|
||||
|
||||
const menuType = form.watch("menuLnkgTypeCd");
|
||||
const isSubmitDisabled = !form.watch("siteId");
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
|
||||
</h3>
|
||||
<ManagementAuthorityDialog menuCd={menuCd} />
|
||||
<>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<MenuHistoryDialog menuCd={menuCd} />
|
||||
<ManagementAuthorityDialog menuCd={menuCd} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<BaseMenuForm form={form} menuCd={menuCd} />
|
||||
<TypeMenuForm form={form} menuType={menuType} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<BaseMenuForm form={form} menuCd={menuCd} />
|
||||
<TypeMenuForm form={form} menuType={menuType} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
|
||||
저장
|
||||
</Button>
|
||||
{isModifyMode && (
|
||||
<Button size="lg" color="red" type="button" onClick={onDelete}>
|
||||
삭제
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
{isModifyMode && (
|
||||
<Button
|
||||
size="lg"
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={handleLoadTemporaryData}
|
||||
>
|
||||
불러오기
|
||||
</Button>
|
||||
)}
|
||||
{isModifyMode && (
|
||||
<Button
|
||||
size="lg"
|
||||
color="gray"
|
||||
type="button"
|
||||
onClick={handleMenuTemporaryStorage}
|
||||
>
|
||||
임시저장
|
||||
</Button>
|
||||
)}
|
||||
{isModifyMode && (
|
||||
<Button
|
||||
size="lg"
|
||||
color="emerald"
|
||||
type="button"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
미리보기
|
||||
</Button>
|
||||
)}
|
||||
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
|
||||
저장
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
{isModifyMode && (
|
||||
<Button size="lg" color="red" type="button" onClick={onDelete}>
|
||||
삭제
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="max-h-[90vh]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>메뉴 미리보기</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex h-full max-h-[80vh] min-h-[60vh] flex-col gap-4 overflow-auto">
|
||||
<LNB
|
||||
currentPath={form.watch("urlPath") ?? ""}
|
||||
propSiteId={form.watch("siteId")}
|
||||
previewMode
|
||||
classNames={{ wrapper: "static h-full border-none p-0" }}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
type PaginationState,
|
||||
type RowSelectionState,
|
||||
} from "@tanstack/react-table";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Input,
|
||||
Label,
|
||||
} from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
|
||||
import { MenuHistoryApi } from "../api/menu-history-service";
|
||||
import type { MenuHistorySearchParams } from "../model/menu-history-interface";
|
||||
import { columns } from "../model/menu-history-columns";
|
||||
|
||||
type MenuHistoryDialogProps = {
|
||||
menuCd?: number;
|
||||
} & React.ComponentProps<typeof Dialog>;
|
||||
|
||||
function MenuHistoryDialog({ menuCd }: MenuHistoryDialogProps) {
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<MenuHistorySearchParams>({
|
||||
keyword: "",
|
||||
keywordType: "menuKornNm",
|
||||
});
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const selectedHistoryId = parseInt(Object.keys(rowSelection)[0] ?? "0");
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_HISTORY, pagination],
|
||||
queryFn: () =>
|
||||
MenuHistoryApi.getMenuHistoryList({
|
||||
...searchParams,
|
||||
menuCd,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
enabled: !!menuCd && open,
|
||||
});
|
||||
|
||||
const { mutateAsync: rollbackMenu } = useMutationWrapper({
|
||||
mutationFn: (hstrySn: number) => MenuHistoryApi.rollbackMenu(hstrySn),
|
||||
onSuccess: () => {
|
||||
alert("메뉴가 성공적으로 복원되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS],
|
||||
});
|
||||
setOpen(false);
|
||||
},
|
||||
});
|
||||
|
||||
const handleRollback = async (hstrySn: number) => {
|
||||
if (!(await confirm("메뉴를 '이전 데이터'로 되돌리시겠습니까?"))) return;
|
||||
await rollbackMenu(hstrySn);
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: { pagination, rowSelection },
|
||||
manualPagination: true,
|
||||
enableRowSelection: true,
|
||||
enableMultiRowSelection: false,
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getRowId: (row) => row.hstrySn.toString(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
query.refetch();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setRowSelection({});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button color="white" disabled={!menuCd}>
|
||||
<i className="diveicon di-history" />
|
||||
이력보기
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[100rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>메뉴 이력 관리</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSearch}>
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<Label>메뉴명</Label>
|
||||
<Input
|
||||
placeholder="검색어를 입력하세요"
|
||||
value={searchParams.keyword ?? ""}
|
||||
onChange={(e) =>
|
||||
setSearchParams({ ...searchParams, keyword: e.target.value })
|
||||
}
|
||||
className="w-[36rem]"
|
||||
/>
|
||||
<Button type="submit">조회</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="max-h-[56rem] overflow-auto">{tableComponent}</div>
|
||||
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button size="lg" color="white">
|
||||
닫기
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={() => handleRollback(selectedHistoryId)}
|
||||
disabled={!selectedHistoryId}
|
||||
>
|
||||
되돌리기
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { MenuHistoryDialog };
|
||||
@@ -4,8 +4,8 @@ import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview";
|
||||
import { Button, cn } from "@common/components";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { MenuApi } from "../api/menu-service";
|
||||
import type { TreeMenu } from "../model/menu-interface";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu } from "../model/tree-menu-interface";
|
||||
import { EditableNode } from "./editable-node";
|
||||
|
||||
type MenuNodeProps = {
|
||||
@@ -37,7 +37,7 @@ function MenuNode({
|
||||
const { mutateAsync: updateMenuName } = useMutationWrapper({
|
||||
mutationFn: (nodeName: string) => {
|
||||
const encodedName = encodeURIComponent(nodeName);
|
||||
return MenuApi.updateMenuName(id as number, encodedName);
|
||||
return TreeMenuApi.updateMenuName(id as number, encodedName);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
|
||||
@@ -14,19 +14,20 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import { SiteApi } from "@/entities/site";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import type { TreeMenu } from "../model/menu-interface";
|
||||
import { MenuApi } from "../api/menu-service";
|
||||
import {
|
||||
MenuApi,
|
||||
MENU_TYPE,
|
||||
menuFormSchema,
|
||||
type MenuFormData,
|
||||
} from "../model/menu-interface";
|
||||
} from "@/entities/menu";
|
||||
import { SiteApi } from "@/entities/site";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { generateMenuTreeData } from "../lib/utils";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu } from "../model/tree-menu-interface";
|
||||
import { MenuTree } from "./menu-tree";
|
||||
import { MenuForm } from "./menu-form";
|
||||
import { generateMenuTreeData } from "../lib/utils";
|
||||
|
||||
function MenuPage() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -46,7 +47,7 @@ function MenuPage() {
|
||||
|
||||
const { data: menuTreeData } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
|
||||
queryFn: () => MenuApi.getTreeMenuList(siteId),
|
||||
queryFn: () => TreeMenuApi.getTreeMenuList(siteId),
|
||||
select: (data) => {
|
||||
const treeData = generateMenuTreeData(data);
|
||||
const defaultSortSeq =
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
import { Button } from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import { MenuApi } from "../api/menu-service";
|
||||
import type { MenuFormData, TreeMenu, TreeNode } from "../model/menu-interface";
|
||||
import type { MenuFormData } from "@/entities/menu";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
|
||||
import { handleDropNode, handleSelectNode } from "../lib/utils";
|
||||
import { MenuNode } from "./menu-node";
|
||||
import { EditableNode } from "./editable-node";
|
||||
@@ -41,7 +42,7 @@ function MenuTree({
|
||||
|
||||
const { mutate: updateTreeMenu } = useMutationWrapper({
|
||||
mutationFn: (newTree: TreeNode) =>
|
||||
MenuApi.updateTreeMenu(siteId, [newTree]),
|
||||
TreeMenuApi.updateTreeMenu(siteId, [newTree]),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS],
|
||||
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
@@ -52,10 +54,22 @@ function OrganizationSearchDialog({
|
||||
});
|
||||
|
||||
const handleSelectNode = (node: NodeModel<TreeModel>) => {
|
||||
if (node.data && "ognzSn" in node.data) return;
|
||||
setSelectedNode(node);
|
||||
handleUserSelect(node.data as TreeUser);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setTree(filterUserOrganizationTreeDataByKeyword(treeData ?? [], keyword));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setTree(treeData ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setTree(treeData ?? []);
|
||||
}, [treeData]);
|
||||
@@ -73,21 +87,25 @@ function OrganizationSearchDialog({
|
||||
<DialogTitle>담당자 조회</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="card card-border !m-0 flex w-full items-center gap-2">
|
||||
<Input
|
||||
className="h-[2.5rem] !text-[1.3rem]"
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setTree(filterUserOrganizationTreeDataByKeyword(tree, keyword))
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-search text-base" />
|
||||
조회
|
||||
</Button>
|
||||
<div className="card card-border !m-0 w-full">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2">
|
||||
<Input
|
||||
className="h-[2.5rem] !text-[1.3rem]"
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button type="submit" size="sm">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="white"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="h-[36rem] overflow-auto">
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
@@ -116,6 +134,16 @@ function OrganizationSearchDialog({
|
||||
</DndProvider>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button color="white" onClick={() => setSelectedNode(null)}>
|
||||
취소
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button disabled={!selectedNode}>확인</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
import type { BoardPopup } from "@/entities/board";
|
||||
import type { Contents } from "@/entities/content";
|
||||
import type { TreeUser } from "@/entities/user-organization";
|
||||
import type { MenuFormData, MenuType } from "@/entities/menu";
|
||||
import { FormItem, FormControl, FormField } from "@/shared/ui/form";
|
||||
import type { MenuFormData, MenuType } from "../model/menu-interface";
|
||||
import { OrganizationSearchDialog } from "./organization-search-dialog";
|
||||
import { ContentSearchDialog } from "./content-search-dialog";
|
||||
import { BoardSearchDialog } from "./board-search-dialog";
|
||||
@@ -171,7 +171,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
|
||||
)}
|
||||
{(menuType === "CONTENTS" || menuType === "BOARD") && (
|
||||
<TableRow>
|
||||
<TableHead>메뉴 평가 사용 여부</TableHead>
|
||||
<TableHead data-required>메뉴 평가 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -237,7 +237,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>출력 여부</TableHead>
|
||||
<TableHead data-required>출력 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -268,7 +268,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead data-required>사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -38,7 +38,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>API명</TableHead>
|
||||
<TableHead data-required>API명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -59,7 +59,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>API URL</TableHead>
|
||||
<TableHead data-required>API URL</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -80,7 +80,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>요청 메소드</TableHead>
|
||||
<TableHead data-required>요청 메소드</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -100,7 +100,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableHead data-required>정렬순서</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -126,7 +126,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용여부</TableHead>
|
||||
<TableHead data-required>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -18,21 +18,23 @@ import { columns } from "../model/authorizations-table-columns";
|
||||
function ModulePage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [authorizationQuery, setAuthorizationQuery] =
|
||||
useState<AuthorizationsParams>({
|
||||
authorizationName: "",
|
||||
httpMethodCode: "",
|
||||
});
|
||||
const [authorizationName, setAuthorizationName] = useState("");
|
||||
const [httpMethodCode, setHttpMethodCode] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<AuthorizationsParams>({
|
||||
authorizationName: "",
|
||||
httpMethodCode: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS, pagination],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
AuthorizationsApi.getAuthorizationsList({
|
||||
...authorizationQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -60,14 +62,27 @@ function ModulePage() {
|
||||
},
|
||||
});
|
||||
|
||||
const handleAuthorizationQueryChange =
|
||||
(key: keyof AuthorizationsParams) => (value: string) => {
|
||||
setAuthorizationQuery({ ...authorizationQuery, [key]: value });
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
authorizationName,
|
||||
httpMethodCode,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setAuthorizationName("");
|
||||
setHttpMethodCode("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
authorizationName: "",
|
||||
httpMethodCode: "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -81,7 +96,8 @@ function ModulePage() {
|
||||
</Label>
|
||||
<CodeSelect
|
||||
upCd="HTTPMHD"
|
||||
onValueChange={handleAuthorizationQueryChange("httpMethodCode")}
|
||||
value={httpMethodCode}
|
||||
onValueChange={(value) => setHttpMethodCode(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -91,18 +107,22 @@ function ModulePage() {
|
||||
<Input
|
||||
id="keyword"
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={authorizationQuery.authorizationName}
|
||||
onChange={(e) =>
|
||||
handleAuthorizationQueryChange("authorizationName")(
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
value={authorizationName}
|
||||
onChange={(e) => setAuthorizationName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -87,7 +87,7 @@ function OrganizationForm({
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>조직명</TableHead>
|
||||
<TableHead data-required>조직명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -106,7 +106,7 @@ function OrganizationForm({
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableHead data-required>정렬순서</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -41,6 +41,10 @@ export class PopupApiService implements PopupService {
|
||||
validatedData,
|
||||
);
|
||||
}
|
||||
|
||||
public async deletePopup(popupSn: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.POPUPS}/${popupSn}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const PopupApi = new PopupApiService();
|
||||
|
||||
@@ -68,4 +68,5 @@ export interface PopupService {
|
||||
getPopupInfo(popupSn: number): Promise<PopupInfo>;
|
||||
createPopup(data: PopupFormData): Promise<void>;
|
||||
updatePopup(popupSn: number, data: PopupUpdateFormData): Promise<void>;
|
||||
deletePopup(popupSn: number): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,13 @@ function ModifyPage({ popupSn }: { popupSn: number }) {
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deletePopup } = useMutationWrapper({
|
||||
mutationFn: () => PopupApi.deletePopup(popupSn),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: savePostingHistory } = useMutationWrapper({
|
||||
mutationFn: (data: PostPayload[]) => PostingApi.savePosting(data),
|
||||
onSuccess: () => {
|
||||
@@ -101,7 +108,12 @@ function ModifyPage({ popupSn }: { popupSn: number }) {
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">팝업 수정</h3>
|
||||
</div>
|
||||
<PopupForm form={form} handleSave={updatePopup} />
|
||||
<PopupForm
|
||||
form={form}
|
||||
popupSn={popupSn}
|
||||
handleSave={updatePopup}
|
||||
handleDelete={deletePopup}
|
||||
/>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
|
||||
@@ -32,7 +32,7 @@ const Editor = dynamic(
|
||||
|
||||
type PopupFormProps<T extends PopupFormData | PopupUpdateFormData> = {
|
||||
form: UseFormReturn<T>;
|
||||
popupSn?: string;
|
||||
popupSn?: number;
|
||||
handleSave: (data: T) => Promise<void>;
|
||||
handleDelete?: () => Promise<void>;
|
||||
};
|
||||
@@ -80,7 +80,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>제목</TableHead>
|
||||
<TableHead data-required>제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -101,7 +101,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 크기</TableHead>
|
||||
<TableHead data-required>팝업 크기</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-1">
|
||||
<FormField
|
||||
@@ -149,7 +149,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 위치</TableHead>
|
||||
<TableHead data-required>팝업 위치</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-1">
|
||||
<FormField
|
||||
@@ -197,7 +197,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 유형</TableHead>
|
||||
<TableHead data-required>팝업 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -216,7 +216,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>콘텐츠 유형</TableHead>
|
||||
<TableHead data-required>콘텐츠 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -297,7 +297,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>팝업 그만 보기 사용 여부</TableHead>
|
||||
<TableHead data-required>팝업 그만 보기 사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -327,7 +327,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead data-required>사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -25,8 +25,10 @@ type PostingTarget = {
|
||||
function PopupPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [popupParams, setPopupParams] = useState<PopupParams>({
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<PopupParams>({
|
||||
keyword: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -36,10 +38,10 @@ function PopupPage() {
|
||||
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
PopupApi.getPopupList({
|
||||
...popupParams,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -48,7 +50,22 @@ function PopupPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Popup) => {
|
||||
@@ -99,14 +116,20 @@ function PopupPage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={popupParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPopupParams({ ...popupParams, keyword: e.target.value })
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -27,7 +27,7 @@ export const columns: ColumnDef<PrivacyLog>[] = [
|
||||
},
|
||||
{
|
||||
accessorKey: "rqstrNm",
|
||||
header: "조회자명",
|
||||
header: "요청자명",
|
||||
},
|
||||
{
|
||||
accessorKey: "rqstrIp",
|
||||
|
||||
@@ -3,65 +3,54 @@
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import {
|
||||
getCoreRowModel,
|
||||
type PaginationState,
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
} from "@common/components";
|
||||
import { PrivacyLogPrivacy } from "@/entities/privacylog";
|
||||
import { type PrivacyLogParams} from "@/entities/privacylog/model/privacylog-interface";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import type { PrivacyLogParams } from "@/entities/privacylog/model/privacylog-interface";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/privacylog/model/table-columns";
|
||||
|
||||
|
||||
|
||||
type DateRange = {
|
||||
from: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
function PrivacyLogPage({ }) {
|
||||
|
||||
// 1주일 전부터 오늘까지 기본 날짜 설정
|
||||
function PrivacyLogPage() {
|
||||
const defaultDateRange: DateRange = {
|
||||
from: dayjs().subtract(7, "day").toDate(),
|
||||
to: dayjs().toDate(),
|
||||
};
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const [privacylogPostQuery, setprivacylogPostQuery] = useState< PrivacyLogParams>({
|
||||
siteId: "",
|
||||
inqInfoKeywordType: "userName",
|
||||
inqInfoKeyword: "",
|
||||
rqstrIp: "",
|
||||
inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
inqDtEnd: dayjs().format("YYYY-MM-DD"),
|
||||
sort:"inqDt,asc"
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
const [inqInfoKeyword, setInqInfoKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<PrivacyLogParams>({
|
||||
inqInfoKeywordType: "userName",
|
||||
inqInfoKeyword: "",
|
||||
inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
inqDtEnd: dayjs().format("YYYY-MM-DD"),
|
||||
sort: "inqDt,desc",
|
||||
});
|
||||
|
||||
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.PRIVACYLOG, pagination],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.PRIVACYLOG, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
PrivacyLogPrivacy.getPrivacyLogList({
|
||||
...privacylogPostQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -87,14 +76,29 @@ function PrivacyLogPage({ }) {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
inqInfoKeyword,
|
||||
inqDtStart: dayjs(dateRange.from).format("YYYY-MM-DD"),
|
||||
inqDtEnd: dayjs(dateRange.to).format("YYYY-MM-DD"),
|
||||
});
|
||||
};
|
||||
|
||||
const handleprivacylogParamsChange =
|
||||
(key: keyof PrivacyLogParams) => (value: unknown) => {
|
||||
setprivacylogPostQuery({ ...privacylogPostQuery, [key]: value });
|
||||
const handleReset = () => {
|
||||
setInqInfoKeyword("");
|
||||
setDateRange(defaultDateRange);
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
inqInfoKeyword: "",
|
||||
inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
inqDtEnd: dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handlePresetDateRange = (value: string) => {
|
||||
let presetDateRange: DateRange | undefined;
|
||||
switch (value) {
|
||||
@@ -119,23 +123,9 @@ function PrivacyLogPage({ }) {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
handleDateRangeChange(presetDateRange);
|
||||
setDateRange(presetDateRange || defaultDateRange);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleDateRangeChange = (value: DateRange | undefined) => {
|
||||
setDateRange(value || defaultDateRange);
|
||||
setprivacylogPostQuery({
|
||||
...privacylogPostQuery,
|
||||
inqDtStart: dayjs(value?.from).format("YYYY-MM-DD") ?? dayjs().subtract(7, "day").format("YYYY-MM-DD"),
|
||||
inqDtEnd: dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex-1">
|
||||
<div className="card card-border">
|
||||
@@ -143,7 +133,7 @@ function PrivacyLogPage({ }) {
|
||||
<Label className="mr-2">조회기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={handleDateRangeChange}
|
||||
onValueChange={(value) => setDateRange(value || defaultDateRange)}
|
||||
>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
@@ -158,24 +148,32 @@ function PrivacyLogPage({ }) {
|
||||
</Select>
|
||||
</div>
|
||||
</DateRangePicker>
|
||||
<Label className="ml-4 mr-2 text-label">조회자명</Label>
|
||||
<Label className="ml-4 mr-2 text-label">조회정보</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={privacylogPostQuery.inqInfoKeyword}
|
||||
onChange={(e) =>
|
||||
handleprivacylogParamsChange("inqInfoKeyword")(e.target.value)
|
||||
}
|
||||
value={inqInfoKeyword}
|
||||
onChange={(e) => setInqInfoKeyword(e.target.value)}
|
||||
/>
|
||||
|
||||
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h3 className="text-2xl font-medium text-label">로그인로그 목록</h3>
|
||||
<h3 className="text-2xl font-medium text-label">
|
||||
개인정보 조회 로그 목록
|
||||
</h3>
|
||||
<div className="flex gap-2">
|
||||
<Select
|
||||
value={table.getState().pagination.pageSize.toString()}
|
||||
@@ -196,7 +194,7 @@ function PrivacyLogPage({ }) {
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] !p-0">{tableComponent}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,28 +51,43 @@ function RecentPage() {
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRange>();
|
||||
const [dateRange, setDateRange] = useState<DateRange | undefined>({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
});
|
||||
const [tableSort, setTableSort] = useState("regDt,desc");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [recentPostQuery, setRecentPostQuery] = useState<RecentPostParams>({
|
||||
|
||||
const [keywordType, setKeywordType] = useState("");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [bbsId, setBbsId] = useState<string | null>(null);
|
||||
const [delYn, setDelYn] = useState("N");
|
||||
|
||||
const [searchParams, setSearchParams] = useState<RecentPostParams>({
|
||||
keywordType: "",
|
||||
keyword: "",
|
||||
bbsId: null,
|
||||
delYn: "N",
|
||||
frDateYmd: "",
|
||||
toDateYmd: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [isDeletePostData, setIsDeletePostData] = useState(false);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, pagination, tableSort],
|
||||
queryKey: [
|
||||
ENDPOINT.BOARD_SERVICE.POSTS,
|
||||
searchParams,
|
||||
pagination,
|
||||
tableSort,
|
||||
],
|
||||
queryFn: () =>
|
||||
PostApi.getRecentPostList({
|
||||
...recentPostQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
sort: tableSort,
|
||||
@@ -115,14 +130,44 @@ function RecentPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDeletePostData(recentPostQuery.delYn === "Y");
|
||||
query.refetch();
|
||||
setIsDeletePostData(delYn === "Y");
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType,
|
||||
keyword,
|
||||
bbsId: bbsId ? parseInt(bbsId) : null,
|
||||
delYn,
|
||||
frDateYmd: dateRange?.from
|
||||
? dayjs(dateRange.from).format("YYYYMMDD")
|
||||
: "",
|
||||
toDateYmd: dateRange?.to ? dayjs(dateRange.to).format("YYYYMMDD") : "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRecentPostParamsChange =
|
||||
(key: keyof RecentPostParams) => (value: unknown) => {
|
||||
setRecentPostQuery({ ...recentPostQuery, [key]: value });
|
||||
};
|
||||
const handleReset = () => {
|
||||
setKeywordType("");
|
||||
setKeyword("");
|
||||
setBbsId(null);
|
||||
setDelYn("N");
|
||||
setDateRange({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType: "",
|
||||
keyword: "",
|
||||
bbsId: null,
|
||||
delYn: "N",
|
||||
frDateYmd: "",
|
||||
toDateYmd: "",
|
||||
});
|
||||
setIsDeletePostData(false);
|
||||
};
|
||||
|
||||
const handlePresetDateRange = (value: string) => {
|
||||
let presetDateRange: DateRange | undefined;
|
||||
@@ -148,16 +193,7 @@ function RecentPage() {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
handleDateRangeChange(presetDateRange);
|
||||
};
|
||||
|
||||
const handleDateRangeChange = (value: DateRange | undefined) => {
|
||||
setDateRange(value);
|
||||
setRecentPostQuery({
|
||||
...recentPostQuery,
|
||||
frDateYmd: dayjs(value?.from).format("YYYYMMDD") ?? "",
|
||||
toDateYmd: dayjs(value?.to).format("YYYYMMDD") ?? "",
|
||||
});
|
||||
setDateRange(presetDateRange);
|
||||
};
|
||||
|
||||
const onSoftDeletePost = async () => {
|
||||
@@ -214,10 +250,7 @@ function RecentPage() {
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2">날짜</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={handleDateRangeChange}
|
||||
>
|
||||
<DateRangePicker value={dateRange} onValueChange={setDateRange}>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
@@ -233,7 +266,10 @@ function RecentPage() {
|
||||
</DateRangePicker>
|
||||
|
||||
<Label className="ml-4 mr-2 text-label">게시판</Label>
|
||||
<Select onValueChange={handleRecentPostParamsChange("bbsId")}>
|
||||
<Select
|
||||
value={bbsId?.toString() || ""}
|
||||
onValueChange={(value) => setBbsId(value ? value : null)}
|
||||
>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
@@ -247,7 +283,7 @@ function RecentPage() {
|
||||
</Select>
|
||||
|
||||
<Label className="ml-4 mr-2 text-label">상태</Label>
|
||||
<Select onValueChange={handleRecentPostParamsChange("delYn")}>
|
||||
<Select value={delYn} onValueChange={(value) => setDelYn(value)}>
|
||||
<SelectTrigger className="w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
@@ -259,8 +295,8 @@ function RecentPage() {
|
||||
|
||||
<Label className="ml-4 mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={recentPostQuery.keywordType}
|
||||
onValueChange={handleRecentPostParamsChange("keywordType")}
|
||||
value={keywordType}
|
||||
onValueChange={(value) => setKeywordType(value)}
|
||||
>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
@@ -272,15 +308,21 @@ function RecentPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={recentPostQuery.keyword}
|
||||
onChange={(e) =>
|
||||
handleRecentPostParamsChange("keyword")(e.target.value)
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -53,7 +53,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>권한ID</TableHead>
|
||||
<TableHead data-required>권한ID</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -95,7 +95,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>권한명</TableHead>
|
||||
<TableHead data-required>권한명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -116,7 +116,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>권한내용</TableHead>
|
||||
<TableHead data-required>권한내용</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -137,7 +137,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>관리권한여부</TableHead>
|
||||
<TableHead data-required>관리권한여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -166,7 +166,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용여부</TableHead>
|
||||
<TableHead data-required>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -13,8 +13,10 @@ import { columns } from "../model/table-columns";
|
||||
function RolePage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [roleQuery, setRoleQuery] = useState<RoleParams>({
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<RoleParams>({
|
||||
keyword: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -22,10 +24,10 @@ function RolePage() {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.ROLES, pagination],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.ROLES, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
RoleApi.getRoles({
|
||||
...roleQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -54,7 +56,22 @@ function RolePage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -66,14 +83,20 @@ function RolePage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={roleQuery.keyword}
|
||||
onChange={(e) =>
|
||||
setRoleQuery({ ...roleQuery, keyword: e.target.value })
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -34,6 +34,10 @@ class ScheduleApiService implements ScheduleService {
|
||||
): Promise<void> {
|
||||
await client.put(`${ENDPOINT.CMS_SERVICE.SCHEDULES}/${schdlSn}`, data);
|
||||
}
|
||||
|
||||
public async deleteSchedule(schdlSn: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.SCHEDULES}/${schdlSn}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const ScheduleApi = new ScheduleApiService();
|
||||
|
||||
@@ -71,4 +71,5 @@ export interface ScheduleService {
|
||||
getScheduleInfo(schdlSn: number): Promise<Schedule>;
|
||||
createSchedule(data: ScheduleFormData): Promise<void>;
|
||||
updateSchedule(schdlSn: number, data: ScheduleUpdateFormData): Promise<void>;
|
||||
deleteSchedule(schdlSn: number): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ function ModifyPage({ schdlSn }: { schdlSn: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteSchedule } = useMutationWrapper({
|
||||
mutationFn: () => ScheduleApi.deleteSchedule(parseInt(schdlSn)),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SCHEDULES],
|
||||
});
|
||||
alert("일정이 삭제되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<ScheduleUpdateFormData>({
|
||||
resolver: zodResolver(scheduleUpdateFormSchema),
|
||||
values: {
|
||||
@@ -57,7 +67,12 @@ function ModifyPage({ schdlSn }: { schdlSn: string }) {
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">스케줄 수정</h3>
|
||||
</div>
|
||||
<ScheduleForm form={form} handleSave={handleSave} />
|
||||
<ScheduleForm
|
||||
form={form}
|
||||
scheduleId={schdlSn}
|
||||
handleSave={handleSave}
|
||||
handleDelete={deleteSchedule}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ function ScheduleForm<T extends ScheduleFormData | ScheduleUpdateFormData>({
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>제목</TableHead>
|
||||
<TableHead data-required>제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -87,7 +87,7 @@ function ScheduleForm<T extends ScheduleFormData | ScheduleUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>일정 기간</TableHead>
|
||||
<TableHead data-required>일정 기간</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -243,7 +243,7 @@ function ScheduleForm<T extends ScheduleFormData | ScheduleUpdateFormData>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>일정 유형</TableHead>
|
||||
<TableHead data-required>일정 유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -70,7 +70,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사이트 구분</TableHead>
|
||||
<TableHead data-required>사이트 구분</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex gap-2">
|
||||
<FormField
|
||||
@@ -122,7 +122,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사이트명</TableHead>
|
||||
<TableHead data-required>사이트명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -287,7 +287,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>기본 사이트 여부</TableHead>
|
||||
<TableHead data-required>기본 사이트 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -316,7 +316,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead data-required>사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -14,10 +14,10 @@ import { columns } from "../model/table-columns";
|
||||
|
||||
function SitePage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [siteQuery, setSiteQuery] = useState<SiteParams>({
|
||||
const [siteNm, setSiteNm] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<SiteParams>({
|
||||
siteNm: "",
|
||||
sort:"regDt,desc"
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -25,10 +25,10 @@ function SitePage() {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SITES, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SITES, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
SiteApi.getSiteList({
|
||||
...siteQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -37,7 +37,18 @@ function SitePage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
siteNm,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSiteNm("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
siteNm: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Site) => {
|
||||
@@ -65,17 +76,26 @@ function SitePage() {
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label htmlFor="" className="mr-2 text-label">
|
||||
<Label htmlFor="siteNm" className="mr-2 text-label">
|
||||
사이트명
|
||||
</Label>
|
||||
<Input
|
||||
name="siteNm"
|
||||
value={siteNm}
|
||||
onChange={(e) => setSiteNm(e.target.value)}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={siteQuery.siteNm}
|
||||
onChange={(e) => setSiteQuery({ siteNm: e.target.value })}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
<Button type="submit" className="h-[3.2rem]">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -45,6 +45,10 @@ export class SurveyApiService implements SurveyService {
|
||||
await client.put(`${ENDPOINT.CMS_SERVICE.SURVEY}/${srvySn}`, data);
|
||||
}
|
||||
|
||||
public async deleteSurvey(srvySn: number): Promise<void> {
|
||||
await client.delete(`${ENDPOINT.CMS_SERVICE.SURVEY}/${srvySn}`);
|
||||
}
|
||||
|
||||
public async getQuestionList(params: QuestionParams): Promise<QuestionList> {
|
||||
const response = await client.get<QuestionList>(
|
||||
`${ENDPOINT.CMS_SERVICE.SURVEY_QUESTION}`,
|
||||
|
||||
@@ -29,6 +29,13 @@ function DetailPage({ srvySn }: { srvySn: string }) {
|
||||
queryFn: () => SurveyApi.getSurveyInfo(parseInt(srvySn)),
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteSurvey } = useMutationWrapper({
|
||||
mutationFn: () => SurveyApi.deleteSurvey(parseInt(srvySn)),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
const postHistoryQuery = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, srvySn],
|
||||
queryFn: () =>
|
||||
@@ -77,6 +84,12 @@ function DetailPage({ srvySn }: { srvySn: string }) {
|
||||
},
|
||||
});
|
||||
|
||||
const handleDeleteSurvey = async () => {
|
||||
if (await confirm("삭제하시겠습니까?")) {
|
||||
deleteSurvey();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeletePostingHistory = async () => {
|
||||
const data = postingTable.getFilteredSelectedRowModel().rows.map((row) => ({
|
||||
...row.original,
|
||||
@@ -101,7 +114,9 @@ function DetailPage({ srvySn }: { srvySn: string }) {
|
||||
<Button onClick={() => router.push(`${pathname}/modify`)}>
|
||||
수정
|
||||
</Button>
|
||||
<Button color="red">삭제</Button>
|
||||
<Button color="red" onClick={handleDeleteSurvey}>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="flex flex-col">
|
||||
|
||||
@@ -41,7 +41,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>문항순번</TableHead>
|
||||
<TableHead data-required>문항순번</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -66,7 +66,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>문항유형</TableHead>
|
||||
<TableHead data-required>문항유형</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -87,7 +87,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>문항내용</TableHead>
|
||||
<TableHead data-required>문항내용</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -29,7 +29,7 @@ function SurveyForm({ form, ...props }: SurveyFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>설문 제목</TableHead>
|
||||
<TableHead data-required>설문 제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -50,7 +50,7 @@ function SurveyForm({ form, ...props }: SurveyFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>설문 목적</TableHead>
|
||||
<TableHead data-required>설문 목적</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -25,21 +25,23 @@ type PostingTarget = {
|
||||
function SurveyPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [surveyParams, setSurveyParams] = useState<SurveyParams>({
|
||||
const [srvyTtl, setSrvyTtl] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<SurveyParams>({
|
||||
srvyTtl: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
SurveyApi.getSurveyList({
|
||||
...surveyParams,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -71,7 +73,22 @@ function SurveyPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
srvyTtl,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setSrvyTtl("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
srvyTtl: "",
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -99,14 +116,20 @@ function SurveyPage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={surveyParams.srvyTtl}
|
||||
onChange={(e) =>
|
||||
setSurveyParams({ ...surveyParams, srvyTtl: e.target.value })
|
||||
}
|
||||
value={srvyTtl}
|
||||
onChange={(e) => setSrvyTtl(e.target.value)}
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -39,7 +39,7 @@ function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>템플릿 구분</TableHead>
|
||||
<TableHead data-required>템플릿 구분</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -59,7 +59,7 @@ function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>템플릿명</TableHead>
|
||||
<TableHead data-required>템플릿명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -80,7 +80,7 @@ function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>템플릿 내용</TableHead>
|
||||
<TableHead data-required>템플릿 내용</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -14,22 +14,23 @@ import type { Template, TemplateParams } from "../model/template-interface";
|
||||
|
||||
function TemplatePage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [templateQuery, setTemplateQuery] = useState<TemplateParams>({
|
||||
tmpltSeCd: "",
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [tmpltSeCd, setTmpltSeCd] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<TemplateParams>({
|
||||
keyword: "",
|
||||
sort:"regDt,desc"
|
||||
tmpltSeCd: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.TEMPLATE, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.TEMPLATE, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
TemplateApi.getTemplateList({
|
||||
...templateQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -38,7 +39,21 @@ function TemplatePage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
tmpltSeCd,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setTmpltSeCd("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
tmpltSeCd: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Template) => {
|
||||
@@ -73,10 +88,8 @@ function TemplatePage() {
|
||||
</Label>
|
||||
<CodeSelect
|
||||
upCd="TMPLTCLCD"
|
||||
value={templateQuery.tmpltSeCd}
|
||||
onValueChange={(value) =>
|
||||
setTemplateQuery({ ...templateQuery, tmpltSeCd: value })
|
||||
}
|
||||
value={tmpltSeCd}
|
||||
onValueChange={(value) => setTmpltSeCd(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -85,19 +98,22 @@ function TemplatePage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={templateQuery.keyword}
|
||||
onChange={(e) =>
|
||||
setTemplateQuery({
|
||||
...templateQuery,
|
||||
keyword: e.target.value,
|
||||
})
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -39,7 +39,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>약관 구분</TableHead>
|
||||
<TableHead data-required>약관 구분</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -59,7 +59,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>약관명</TableHead>
|
||||
<TableHead data-required>약관명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -80,7 +80,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>약관 내용</TableHead>
|
||||
<TableHead data-required>약관 내용</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -99,7 +99,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead data-required>사용 여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -15,21 +15,23 @@ import type { TermsParams, Terms } from "../model/terms-interface";
|
||||
function TermsPage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [termsQuery, setTermsQuery] = useState<TermsParams>({
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [trmsSeCd, setTrmsSeCd] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<TermsParams>({
|
||||
keyword: "",
|
||||
trmsSeCd: "",
|
||||
keyword: "",
|
||||
sort:"regDt,desc"
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 5,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.TERMS, pagination],
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.TERMS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
TermsApi.getTermsList({
|
||||
...termsQuery,
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -38,7 +40,25 @@ function TermsPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
trmsSeCd,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setTrmsSeCd("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
trmsSeCd: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Terms) => {
|
||||
@@ -73,10 +93,8 @@ function TermsPage() {
|
||||
</Label>
|
||||
<CodeSelect
|
||||
upCd="TRMSCLCD"
|
||||
value={termsQuery.trmsSeCd}
|
||||
onValueChange={(value) =>
|
||||
setTermsQuery({ ...termsQuery, trmsSeCd: value })
|
||||
}
|
||||
value={trmsSeCd}
|
||||
onValueChange={(value) => setTrmsSeCd(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -85,19 +103,22 @@ function TermsPage() {
|
||||
</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={termsQuery.keyword}
|
||||
onChange={(e) =>
|
||||
setTermsQuery({
|
||||
...termsQuery,
|
||||
keyword: e.target.value,
|
||||
})
|
||||
}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
|
||||
@@ -50,6 +50,7 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
|
||||
queryFn: () =>
|
||||
LwpoCodeApi.getLwpoCodeList({
|
||||
...searchQuery,
|
||||
upCd: "AGENCYCD",
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -70,6 +71,9 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
|
||||
{
|
||||
accessorKey: "lwpoCd",
|
||||
header: "기관코드",
|
||||
meta: {
|
||||
width: 200,
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "lwpoCdNm",
|
||||
@@ -120,11 +124,13 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
|
||||
<DialogTrigger asChild>
|
||||
<Button className="px-6">기관 검색</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
|
||||
<DialogContent
|
||||
className="max-h-[80vh] max-w-[60rem] overflow-auto"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>기관 검색</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
@@ -150,9 +156,7 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{tableComponent}
|
||||
|
||||
<DialogFooter className="!justify-center">
|
||||
<Button
|
||||
size="lg"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import dayjs from "dayjs";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
@@ -18,10 +19,11 @@ import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
function UserDetail({ userUnqId }: { userUnqId: string }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const { data } = useQuery({
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
|
||||
queryFn: () => UserApi.findByUserUnqId(userUnqId),
|
||||
enabled: !!userUnqId,
|
||||
@@ -31,9 +33,7 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
|
||||
mutationFn: () => UserApi.lockUser(userUnqId),
|
||||
onSuccess: () => {
|
||||
alert("계정 잠금이 활성화 되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
|
||||
});
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -41,9 +41,7 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
|
||||
mutationFn: () => UserApi.unlockUser(userUnqId),
|
||||
onSuccess: () => {
|
||||
alert("계정 잠금이 해제되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
|
||||
});
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,12 +49,26 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
|
||||
mutationFn: () => UserApi.applyUser(userUnqId),
|
||||
onSuccess: () => {
|
||||
alert("사용자 승인이 완료되었습니다.");
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteUser } = useMutationWrapper({
|
||||
mutationFn: () => UserApi.deleteUser(userUnqId),
|
||||
onSuccess: () => {
|
||||
alert("회원이 삭제되었습니다.", router.back);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (await confirm("삭제하시겠습니까?")) {
|
||||
await deleteUser();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card-header">
|
||||
@@ -164,7 +176,7 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
|
||||
<Button size="lg" onClick={() => router.push(`${pathname}/modify`)}>
|
||||
수정
|
||||
</Button>
|
||||
<Button size="lg" color="red">
|
||||
<Button size="lg" color="red" onClick={handleDelete}>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
}, [laeSeCd, watchDtlInstCd]);
|
||||
|
||||
const enoCdSelectData = useMemo(
|
||||
() => laeSeCd?.filter((code) => code.unit?.startsWith("G2")) ?? [],
|
||||
() => laeSeCd?.filter((code) => code.lwpoCd?.startsWith("G2")) ?? [],
|
||||
[laeSeCd],
|
||||
);
|
||||
|
||||
@@ -179,7 +179,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
<TableBody>
|
||||
{/* 아이디 */}
|
||||
<TableRow>
|
||||
<TableHead>아이디</TableHead>
|
||||
<TableHead data-required>아이디</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -223,7 +223,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
|
||||
{/* 비밀번호 */}
|
||||
<TableRow>
|
||||
<TableHead>비밀번호</TableHead>
|
||||
<TableHead data-required>비밀번호</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -270,7 +270,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
|
||||
{/* 비밀번호 힌트 */}
|
||||
<TableRow>
|
||||
<TableHead>비밀번호 힌트</TableHead>
|
||||
<TableHead data-required>비밀번호 힌트</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -316,7 +316,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
|
||||
{/* 이름 */}
|
||||
<TableRow>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead data-required>이름</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -489,7 +489,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
|
||||
{/* 기관구분 */}
|
||||
<TableRow>
|
||||
<TableHead>기관구분</TableHead>
|
||||
<TableHead data-required>기관구분</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -571,7 +571,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
|
||||
|
||||
{/* 소속 */}
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead data-required>소속</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
@@ -40,19 +39,25 @@ function UsersPage() {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [dateRange, setDateRange] = useState<DateRange>();
|
||||
const [dateRange, setDateRange] = useState<DateRange | undefined>({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
});
|
||||
const [keywordType, setKeywordType] = useState("all");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [userStateCode, setUserStateCode] = useState("");
|
||||
const [aprvYn, setAprvYn] = useState("");
|
||||
const [grpId, setGrpId] = useState("");
|
||||
|
||||
const { register, watch, setValue, getValues } = useForm<UserSearchParams>({
|
||||
defaultValues: {
|
||||
keywordType: "all",
|
||||
keyword: "",
|
||||
userStateCode: "",
|
||||
aprvYn: "",
|
||||
grpId: "",
|
||||
frDateYmd: "",
|
||||
toDateYmd: "",
|
||||
sort: "regDt,desc"
|
||||
},
|
||||
const [searchParams, setSearchParams] = useState<UserSearchParams>({
|
||||
keywordType: "all",
|
||||
keyword: "",
|
||||
userStateCode: "",
|
||||
aprvYn: "",
|
||||
grpId: "",
|
||||
frDateYmd: "",
|
||||
toDateYmd: "",
|
||||
sort: "regDt,desc",
|
||||
});
|
||||
|
||||
const { data: userSttsStats } = useQuery({
|
||||
@@ -61,16 +66,16 @@ function UsersPage() {
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination, watch("sort")],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParams, pagination],
|
||||
queryFn: () =>
|
||||
UserApi.findPageUser({
|
||||
...getValues(),
|
||||
...searchParams,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
});
|
||||
|
||||
const { data: userStateCode } = useQuery({
|
||||
const { data: userStateCodes } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CODE],
|
||||
queryFn: () => CodeApi.getCode("USERSTTS"),
|
||||
});
|
||||
@@ -98,7 +103,44 @@ function UsersPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
query.refetch();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType,
|
||||
keyword,
|
||||
userStateCode,
|
||||
aprvYn,
|
||||
grpId,
|
||||
frDateYmd: dateRange?.from
|
||||
? dayjs(dateRange.from).format("YYYYMMDD")
|
||||
: "",
|
||||
toDateYmd: dateRange?.to ? dayjs(dateRange.to).format("YYYYMMDD") : "",
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeywordType("all");
|
||||
setKeyword("");
|
||||
setUserStateCode("");
|
||||
setAprvYn("");
|
||||
setGrpId("");
|
||||
setDateRange({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
});
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keywordType: "all",
|
||||
keyword: "",
|
||||
userStateCode: "",
|
||||
aprvYn: "",
|
||||
grpId: "",
|
||||
frDateYmd: "",
|
||||
toDateYmd: "",
|
||||
});
|
||||
};
|
||||
|
||||
const handlePresetDateRange = (value: string) => {
|
||||
@@ -126,8 +168,6 @@ function UsersPage() {
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
setDateRange(presetDateRange);
|
||||
setValue("frDateYmd", dayjs(presetDateRange?.from).format("YYYYMMDD"));
|
||||
setValue("toDateYmd", dayjs(presetDateRange?.to).format("YYYYMMDD"));
|
||||
};
|
||||
|
||||
const getUserSttsStatsCnt = (code: string): number => {
|
||||
@@ -135,6 +175,21 @@ function UsersPage() {
|
||||
return userSttsStats.find((item) => item.sttsCd === code)?.cnt ?? 0;
|
||||
};
|
||||
|
||||
const handleUserStateCodeChange = (code: string, checked: boolean) => {
|
||||
let newUserStateCode = userStateCode;
|
||||
|
||||
if (checked) {
|
||||
newUserStateCode = userStateCode ? `${userStateCode},${code}` : code;
|
||||
} else {
|
||||
newUserStateCode = userStateCode
|
||||
.split(",")
|
||||
.filter((c) => c !== code)
|
||||
.join(",");
|
||||
}
|
||||
|
||||
setUserStateCode(newUserStateCode);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border flex items-center justify-center gap-10 !bg-accent !py-6">
|
||||
@@ -195,8 +250,8 @@ function UsersPage() {
|
||||
회원정보
|
||||
</Label>
|
||||
<Select
|
||||
{...register("keywordType")}
|
||||
onValueChange={(value) => setValue("keywordType", value)}
|
||||
value={keywordType}
|
||||
onValueChange={(value) => setKeywordType(value)}
|
||||
>
|
||||
<SelectTrigger className="w-[18rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
@@ -210,7 +265,8 @@ function UsersPage() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
{...register("keyword")}
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="검색어를 입력하세요"
|
||||
/>
|
||||
@@ -221,14 +277,7 @@ function UsersPage() {
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="mr-2 text-label">기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={(value) => {
|
||||
setDateRange(value);
|
||||
setValue("frDateYmd", dayjs(value?.from).format("YYYYMMDD"));
|
||||
setValue("toDateYmd", dayjs(value?.to).format("YYYYMMDD"));
|
||||
}}
|
||||
>
|
||||
<DateRangePicker value={dateRange} onValueChange={setDateRange}>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
@@ -250,24 +299,14 @@ function UsersPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="mr-2 text-label">회원상태</Label>
|
||||
<div className="flex gap-4">
|
||||
{userStateCode?.map((item) => (
|
||||
{userStateCodes?.map((item) => (
|
||||
<div key={item.lwpoCd} className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id={item.lwpoCd}
|
||||
value={item.lwpoCd}
|
||||
checked={watch("userStateCode")?.includes(item.lwpoCd)}
|
||||
checked={userStateCode?.includes(item.lwpoCd)}
|
||||
onCheckedChange={(checked) => {
|
||||
setValue(
|
||||
"userStateCode",
|
||||
checked
|
||||
? getValues("userStateCode")
|
||||
? `${getValues("userStateCode")},${item.lwpoCd}`
|
||||
: item.lwpoCd
|
||||
: getValues("userStateCode")
|
||||
?.split(",")
|
||||
.filter((code) => code !== item.lwpoCd)
|
||||
.join(","),
|
||||
);
|
||||
handleUserStateCodeChange(item.lwpoCd, !!checked);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={item.lwpoCd}>{item.lwpoCdNm}</Label>
|
||||
@@ -276,10 +315,20 @@ function UsersPage() {
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<Button type="submit" className="mx-auto w-[16rem]">
|
||||
<i className="diveicon di-search" />
|
||||
검색
|
||||
</Button>
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button type="submit" className="w-[16rem]">
|
||||
<i className="diveicon di-search" />
|
||||
검색
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="w-[16rem]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
@@ -290,7 +339,10 @@ function UsersPage() {
|
||||
<Select
|
||||
defaultValue="regDt,desc"
|
||||
onValueChange={(value) => {
|
||||
setValue("sort", value);
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
sort: value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
|
||||
@@ -20,9 +20,7 @@ export const ENDPOINT = {
|
||||
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
|
||||
ROLES: "/user-service/api/v1/roles",
|
||||
LOGINLOG: "/user-service/api/v1/login-logs",
|
||||
APILOG: "/user-service/api/v1/api-logs",
|
||||
PRIVACYLOG: "/user-service//api/v1/prvc-inq-logs",
|
||||
|
||||
},
|
||||
CMS_SERVICE: {
|
||||
SITES: "/cms-service/api/v1/sites",
|
||||
@@ -33,6 +31,8 @@ export const ENDPOINT = {
|
||||
MENUS: "/cms-service/api/v1/menus",
|
||||
MENU_ROLES: "/cms-service/api/v1/menu-roles",
|
||||
MENU_AUTHRTS: "/cms-service/api/v1/menu-authrts",
|
||||
MENU_TEMPORARY_STORAGE: "/cms-service/api/v1/menu-tmpr-strg",
|
||||
MENU_HISTORY: "/cms-service/api/v1/menu-hstry",
|
||||
CONTENTS: "/cms-service/api/v1/contents",
|
||||
CONTENTS_DETAILS: "/cms-service/api/v1/contents-details",
|
||||
ORGANIZATIONS: "/cms-service/api/v1/ognzs",
|
||||
@@ -52,6 +52,7 @@ export const ENDPOINT = {
|
||||
GROUPS: "/cms-service/api/v1/groups",
|
||||
GROUP_USERS: "/cms-service/api/v1/group-users",
|
||||
MANAGER_IP: "/cms-service/api/v1/mgrip",
|
||||
APILOG: "/cms-service/api/v1/api-logs",
|
||||
},
|
||||
BOARD_SERVICE: {
|
||||
BBS: "/board-service/api/v1/bbs",
|
||||
|
||||
@@ -17,15 +17,13 @@ export const useMutationWrapper = <
|
||||
let errorMessage = "";
|
||||
|
||||
if (error instanceof AxiosError) {
|
||||
if (error.response?.status)
|
||||
errorMessage += `status: ${error.response.status}\n`;
|
||||
if (error.response?.data?.message)
|
||||
errorMessage += `${error.response.data.message}\n`;
|
||||
if (error.response?.data?.errors[0]?.message)
|
||||
errorMessage += `${error.response.data.errors[0].message}\n`;
|
||||
}
|
||||
|
||||
alert(`[Error] ${errorMessage}`);
|
||||
alert(`${errorMessage}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,9 +2,15 @@ import { useEffect, useState } from "react";
|
||||
import { type HttpResponse, Upload } from "tus-js-client";
|
||||
import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "../config";
|
||||
import { useAttachmentStore } from "./use-attachment-store";
|
||||
import { useAlertStore } from "./use-alert-store";
|
||||
import { getCookie } from "./cookie";
|
||||
|
||||
export const useTus = () => {
|
||||
type TusProps = {
|
||||
accept?: string;
|
||||
};
|
||||
|
||||
export const useTus = ({ accept }: TusProps = {}) => {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const { attachmentFileCode, setAttachmentFileCode } = useAttachmentStore(
|
||||
(state) => state,
|
||||
);
|
||||
@@ -13,6 +19,7 @@ export const useTus = () => {
|
||||
const [upload, setUpload] = useState<Upload | null>(null);
|
||||
const [response, setResponse] = useState<HttpResponse | null>(null);
|
||||
const [progress, setProgress] = useState<number>(0);
|
||||
const [isValid, setIsValid] = useState<boolean>(false);
|
||||
|
||||
const metadata = {
|
||||
filename: file?.name ?? "",
|
||||
@@ -20,6 +27,23 @@ export const useTus = () => {
|
||||
atflCd: attachmentFileCode,
|
||||
};
|
||||
|
||||
const validFileType = (file: File) => {
|
||||
const allowedExtensions = accept?.split(",") ?? [];
|
||||
const fileName = file.name;
|
||||
const fileExtension = fileName
|
||||
.substring(fileName.lastIndexOf("."))
|
||||
.toLowerCase();
|
||||
|
||||
if (!allowedExtensions.includes(fileExtension)) {
|
||||
alert("파일 형식이 올바르지 않습니다.");
|
||||
setIsValid(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsValid(true);
|
||||
return true;
|
||||
};
|
||||
|
||||
const createUpload = () => {
|
||||
return new Upload(file!, {
|
||||
endpoint: `${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`,
|
||||
@@ -48,6 +72,7 @@ export const useTus = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!file) return;
|
||||
if (!validFileType(file)) return;
|
||||
|
||||
const upload = createUpload();
|
||||
upload.start();
|
||||
@@ -59,6 +84,7 @@ export const useTus = () => {
|
||||
upload,
|
||||
response,
|
||||
progress,
|
||||
isValid,
|
||||
setFile,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -102,7 +102,7 @@ function GeneralFileUploader<T extends { atflCd?: string | null }>({
|
||||
<span>{attachment.orgnlFileNm}</span>
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex size-7 items-center justify-center rounded-full p-0"
|
||||
className="flex size-7 shrink-0 items-center justify-center rounded-full p-0"
|
||||
onClick={() =>
|
||||
setDeleteAttachmentIds([...deleteAttachmentIds, atflId])
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>제목</TableHead>
|
||||
<TableHead data-required>제목</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -120,7 +120,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
|
||||
</TableRow>
|
||||
{!userInfo?.userUnqId && (
|
||||
<TableRow>
|
||||
<TableHead>비밀번호</TableHead>
|
||||
<TableHead data-required>비밀번호</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
PORT=3000
|
||||
LOG_FILE="start_cs.log"
|
||||
|
||||
# 1. 기존 포트 사용 프로세스 종료
|
||||
PID=$(lsof -t -i :$PORT)
|
||||
if [ -n "$PID" ]; then
|
||||
echo "Killing process on port $PORT (PID: $PID)"
|
||||
kill -9 $PID
|
||||
fi
|
||||
|
||||
# 2. 백그라운드로 앱 시작 (로그 파일로 리디렉션)
|
||||
echo "Starting app on port $PORT..."
|
||||
nohup pnpm start > $LOG_FILE 2>&1 &
|
||||
|
||||
# 3. PID 확인
|
||||
sleep 1
|
||||
NEW_PID=$(lsof -t -i :$PORT)
|
||||
if [ -n "$NEW_PID" ]; then
|
||||
echo "App started successfully (PID: $NEW_PID)"
|
||||
echo "Logs: tail -f $LOG_FILE"
|
||||
else
|
||||
echo "Failed to start app. Check logs: $LOG_FILE"
|
||||
fi
|
||||
@@ -3,8 +3,8 @@ NEXT_PUBLIC_PORT = 11302
|
||||
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11302
|
||||
NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함
|
||||
NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver_swl
|
||||
NEXT_PUBLIC_RSONE_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
|
||||
@@ -3,7 +3,7 @@ NEXT_PUBLIC_PORT = 11301
|
||||
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11301
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
|
||||
NEXT_PUBLIC_TEMP_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
|
||||
+2
-2
@@ -3,12 +3,12 @@ NEXT_PUBLIC_PORT = 3001
|
||||
NEXT_PUBLIC_PROXY_HOST = http://localhost:3001
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
|
||||
NEXT_PUBLIC_TEMP_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
|
||||
# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
NEXT_PUBLIC_SITE_ID = WR
|
||||
NEXT_PUBLIC_PORT = 11301
|
||||
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11301
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
|
||||
+18
-3
@@ -3,23 +3,38 @@ import { BaseLayout } from "@common/components";
|
||||
import "./globals.css";
|
||||
import { StoreProvider } from "./store";
|
||||
import { MainDiv } from "@common/business_components";
|
||||
import koBase from '../public/locales/ko/message.json';
|
||||
import i18next from "i18next";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
i18next.init({
|
||||
lng: 'ko',
|
||||
fallbackLng: 'ko',
|
||||
resources: {
|
||||
ko: {
|
||||
base: koBase,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<I18nextProvider i18n={i18next}>
|
||||
<StoreProvider>
|
||||
<BaseLayout>
|
||||
<MainDiv>
|
||||
<MainDiv>
|
||||
{children}
|
||||
</MainDiv>
|
||||
</MainDiv>
|
||||
</BaseLayout>
|
||||
</StoreProvider>
|
||||
</I18nextProvider>
|
||||
</body>
|
||||
</html>
|
||||
</html >
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
'use client'
|
||||
import { getData, putData } from "../../../sn/app/components/lib/api";
|
||||
import Modal from "../../../sn/app/components/ui/modal";
|
||||
import { VerticalTable } from "../../../sn/app/components/ui/verticalTable";
|
||||
import { useGisContext } from "@common/business_components/ui/map/Gis";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { getLayerContentComponent } from "../../../sn/app/map/components/common";
|
||||
// TODO:
|
||||
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
|
||||
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
|
||||
// const featureId = featureData.getId(); // 피처의 id 가져오기
|
||||
const featureId = typeof featureData.getId === 'function'
|
||||
? featureData.getId()
|
||||
: featureData.fid;
|
||||
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
|
||||
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
|
||||
const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보
|
||||
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
|
||||
const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
|
||||
const [buttonChk, setButtonChk] = useState(true)
|
||||
const [fetchData, setfetchData] = useState<any>(); // api 호출여부
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
|
||||
const [chkMainSsc, setChkMainSsc] = useState(false); // 중점우수토실 여부
|
||||
|
||||
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
|
||||
const getSelectedLayers = () => {
|
||||
let result = null;
|
||||
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
|
||||
let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수
|
||||
layers.forEach((layer: any) => {
|
||||
const source = layer.getSource();
|
||||
if (source && source.hasFeature && featureData
|
||||
&& typeof featureData.getId === 'function' && source.hasFeature(featureData)) {
|
||||
featureLayer = layer; // 피처가 속한 레이어 찾기
|
||||
}
|
||||
});
|
||||
// SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
|
||||
if (featureLayer.get('lyrNm') === null) {
|
||||
if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설';
|
||||
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역';
|
||||
else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실';
|
||||
}
|
||||
else result = featureLayer.get('lyrNm');
|
||||
|
||||
return result;
|
||||
// return featureLayer.get('lyrNm'); // 레이어 이름 출력
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
if (featureId) {
|
||||
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
|
||||
setLayerNm(selectedLayerName); // 상태 업데이트
|
||||
const layerId = featureId.split('.')[0]
|
||||
let data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`);
|
||||
setfetchData(data)
|
||||
switch (selectedLayerName) {
|
||||
case '하수처리시설':
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '하수처리구역':
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '중점우수토실':
|
||||
setButtonChk(false)
|
||||
setChkMainSsc(true)
|
||||
|
||||
// 데이터 전처리: 합쳐진 필드 추가
|
||||
data = data ? {
|
||||
...data,
|
||||
discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}`
|
||||
} : {};
|
||||
|
||||
// 수량/수질측정 정보 데이터 호출
|
||||
const additionalResponse = await getData(`v1/onsemiro/swgRain/${data.adst_dstr_cd}/${data.manage_no}/mntrngs`);
|
||||
setMntrngsData(additionalResponse.content);
|
||||
break;
|
||||
case '우수토실':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수관로':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '오수펌프':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '빗물펌프':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수맨홀':
|
||||
setButtonChk(true)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
setTableData(data);
|
||||
};
|
||||
}
|
||||
fetchData();
|
||||
}, [featureId]);
|
||||
|
||||
// 저장 시 모달 띄우기
|
||||
const handleSaveClick = () => {
|
||||
setIsModalOpen(true); // 모달 열기
|
||||
};
|
||||
|
||||
const handleModalSave = () => {
|
||||
setIsModalOpen(false); // 모달 닫기
|
||||
btnClickEvent(); // 저장 이벤트 실행
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setIsModalOpen(false); // 모달 닫기
|
||||
};
|
||||
|
||||
const btnClickEvent = async () => {
|
||||
const bodyData: Record<string, any> = {}
|
||||
if (buttonChk && contentRef.current) {
|
||||
const inputElements = contentRef.current.querySelectorAll('input');
|
||||
const selectElements = contentRef.current.querySelectorAll('select');
|
||||
// 카멜케이스 변환 함수
|
||||
const toCamelCase = (str: string) =>
|
||||
str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
|
||||
// select 태그의 값 가져오기
|
||||
selectElements.forEach((select) => {
|
||||
const camelCaseId = toCamelCase(select.id);
|
||||
bodyData[camelCaseId] = select.value;
|
||||
});
|
||||
// input 태그의 값 가져오기
|
||||
inputElements.forEach((input) => {
|
||||
const camelCaseId = toCamelCase(input.id);
|
||||
bodyData[camelCaseId] = input.value;
|
||||
});
|
||||
if (layerNm === '하수관로') bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl'
|
||||
else if (layerNm === '우수토실') bodyData['lyrTypeNm'] = 'tb_ms_stofcbr'
|
||||
else if (layerNm === '오수펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
|
||||
else if (layerNm === '빗물펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
|
||||
else if (layerNm === '하수맨홀') bodyData['lyrTypeNm'] = 'tb_ms_swage_mnhl'
|
||||
bodyData['fid'] = featureId
|
||||
const data = await putData(`v1/fclts/${featureId}`, bodyData);
|
||||
if (data.status == 200) {
|
||||
alert('저장되었습니다')
|
||||
setTableData(data)
|
||||
setfetchData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
type ContentComponentProps = {
|
||||
featureData: any;
|
||||
chkMainSsc: boolean;
|
||||
additionalData: any;
|
||||
};
|
||||
const ContentComponent = layerNm ? getLayerContentComponent(layerNm) as React.ComponentType<ContentComponentProps> : null;
|
||||
return (
|
||||
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[102]">
|
||||
<div className="card-header bg-primary flex items-center justify-between p-4">
|
||||
<div className="flex mx-3 flex-col">
|
||||
<h3 className="text-2xl font-medium text-white text-label">{layerNm} 정보</h3>
|
||||
<div className="font-medium text-white text-label">(00구 00리 하수관로)</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-white hover:text-gray-700 focus:outline-none"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto overflow-x-hidden flex-1" ref={contentRef}>
|
||||
<VerticalTable
|
||||
headName='LX 연계정보'
|
||||
tableData={[tableData]}
|
||||
headlist={headlist}
|
||||
pageSize={20}
|
||||
total={0}
|
||||
children={undefined}
|
||||
cellClick={undefined}
|
||||
onPageChange={function (page: number): void {
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
/>
|
||||
<div className="w-full mx-5 flex items-center justify-end space-x-2">
|
||||
{buttonChk ? <Button onClick={handleSaveClick} className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
|
||||
저장
|
||||
</Button> : null}
|
||||
</div>
|
||||
<Modal isModalOpen={isModalOpen} title="자체입력정보 저장" setIsModalOpen={setIsModalOpen} closeModal={handleModalClose} saveModal={handleModalSave}><div className="text-lg">정보를 저장하시겠습니까?</div></Modal>
|
||||
{ContentComponent &&
|
||||
<ContentComponent
|
||||
featureData={fetchData}
|
||||
chkMainSsc={chkMainSsc}
|
||||
additionalData={mntrngsData}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default FeatureClickEventPopup;
|
||||
@@ -1,621 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormField,
|
||||
FormUi,
|
||||
SelectBox,
|
||||
} from "@common/business_components/ui";
|
||||
import { Button, Input } from "@common/components/ui";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { useWrStore } from "@/app/store";
|
||||
import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
|
||||
import {
|
||||
hdtflZod,
|
||||
type customHtdflBdst,
|
||||
} from "@/app/store/ruseFcltMng/fcltReg/htdflReg";
|
||||
import { useEffect } from "react";
|
||||
import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg";
|
||||
import {
|
||||
formatCommaNumber,
|
||||
handleCommaNumberChange,
|
||||
} from "@/components/lib/func/commom";
|
||||
import { formH1Class } from "@/components/lib/class/comClass";
|
||||
|
||||
export default function HtdflPrfmncSttnPopup({
|
||||
htdflForm,
|
||||
isModal,
|
||||
}: {
|
||||
htdflForm: customHtdflBdst;
|
||||
isModal: boolean;
|
||||
}) {
|
||||
const { code, htdflBdstUpdate, htdflBdstReg } = useWrStore((state) => state);
|
||||
|
||||
const defaultValues: z.infer<typeof hdtflZod> = hdtflZod.parse({});
|
||||
|
||||
const form = useForm<z.infer<typeof hdtflZod>>({
|
||||
resolver: zodResolver(hdtflZod),
|
||||
defaultValues: defaultValues,
|
||||
});
|
||||
|
||||
const onSubmit = (values: z.infer<typeof hdtflZod>) => {
|
||||
if (isModal) {
|
||||
htdflBdstReg(values, '41');
|
||||
} else {
|
||||
htdflBdstUpdate(values, '41');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const cleanHtdflData = (raw: any): z.infer<typeof hdtflZod> => {
|
||||
return {
|
||||
...raw,
|
||||
frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null),
|
||||
archChgPrmsnDt: raw?.archChgPrmsnDt
|
||||
? new Date(raw?.archChgPrmsnDt)
|
||||
: null,
|
||||
chgBuilding: (raw?.chgBuilding || []).map((item: any) => ({
|
||||
...item,
|
||||
archChgPrmsnDt:
|
||||
item?.archChgPrmsnDt === null
|
||||
? new Date()
|
||||
: new Date(item?.archChgPrmsnDt),
|
||||
bdstChgNm: item?.bdstChgNm ?? "", // null → ""
|
||||
})),
|
||||
reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({
|
||||
...item,
|
||||
instlCmptnDt: new Date(item?.instlCmptnDt),
|
||||
htdflObj: {
|
||||
...item?.htdflObj,
|
||||
},
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("form errors:", form.formState.errors);
|
||||
}, [form.formState.errors]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hdtflZod) form.reset(cleanHtdflData(htdflForm));
|
||||
}, [htdflForm]);
|
||||
|
||||
return (
|
||||
<div className="!mx-[1.6rem] !p-0r">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
{/* <div className="flex justify-between items-center mx-3"> */}
|
||||
<div>
|
||||
<div className="grid grid-cols-12 flex items-center card-header">
|
||||
<h1 className="text-2xl font-medium text-label col-span-10">온배수 시설 정보</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isModal && htdflForm != undefined ? <BdstReg form={form} /> : <></>}
|
||||
{isModal ? (
|
||||
<h1 className="text-xl col-span-10 mt-4">
|
||||
온배수 시설 정보{" "}
|
||||
</h1>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-6 gap-2 mt-4">
|
||||
<div className="col-span-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.etgtType"
|
||||
render={({ field }) => (
|
||||
<FormUi label="발전유형(코드정비후 수정)">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.fclCpct" // 이름 이상함.
|
||||
render={({ field }) => (
|
||||
<FormUi label="설비용량">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.plwtYn"
|
||||
render={({ field }) => (
|
||||
<FormUi label="담수화 여부">
|
||||
<SelectBox
|
||||
className={""}
|
||||
label={""}
|
||||
selectClass={selectClass}
|
||||
selectArray={code.yn}
|
||||
onSelectValue={field.onChange}
|
||||
value={field.value ?? code.yn[0]?.val}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className=" my-4">
|
||||
<h1 className={formH1Class}>
|
||||
온배수 시설 운영현황
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-10 gap-1 mt-4">
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.orgwtWtritSpt"
|
||||
render={({ field }) => (
|
||||
<FormUi label="원수취수 지점(코드정비후 수정)">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
value={field.value ?? 0}
|
||||
onChange={(e) => {
|
||||
field.onChange(Number(e.target.value));
|
||||
}}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.orgwtOcrnProcs"
|
||||
render={({ field }) => (
|
||||
<FormUi label="원수발생 공정">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type=""
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.wtritEntrcTp"
|
||||
render={({ field }) => (
|
||||
<FormUi label="취수구 온도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.amtgnr"
|
||||
render={({ field }) => (
|
||||
<FormUi label="발생량(m³/년)">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.prcsProcsYn"
|
||||
render={({ field }) => (
|
||||
<FormUi label="처리공정">
|
||||
<SelectBox
|
||||
className={""}
|
||||
label={""}
|
||||
selectClass={selectClass}
|
||||
selectArray={code.yn}
|
||||
onSelectValue={field.onChange}
|
||||
value={field.value ?? code.yn[0]?.val}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.prcsFcltCpct"
|
||||
render={({ field }) => (
|
||||
<FormUi label="처리시설 용량(m³/년)">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.fmYn"
|
||||
render={({ field }) => (
|
||||
<FormUi label="유량계유무">
|
||||
<SelectBox
|
||||
className={""}
|
||||
selectClass={selectClass}
|
||||
label={""}
|
||||
selectArray={code.yn}
|
||||
onSelectValue={field.onChange}
|
||||
value={field.value ?? code.yn[0]?.val}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.utztnCpct"
|
||||
render={({ field }) => (
|
||||
<FormUi label="이용량(m³/년)">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.utztnUsg"
|
||||
render={({ field }) => (
|
||||
<FormUi label="이용용도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="1,000,000"
|
||||
type="text"
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className=" my-2">
|
||||
<h1 className={formH1Class}>
|
||||
요구수질
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.grssClfg"
|
||||
render={({ field }) => (
|
||||
<FormUi label="총대장균군">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.tn"
|
||||
render={({ field }) => (
|
||||
<FormUi label="총질소">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.combResdch"
|
||||
render={({ field }) => (
|
||||
<FormUi label="결합잔류염소">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-6 ">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.tph"
|
||||
render={({ field }) => (
|
||||
<FormUi label="총인">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.trbt"
|
||||
render={({ field }) => (
|
||||
<FormUi label="탁도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.ph"
|
||||
render={({ field }) => (
|
||||
<FormUi label="수소이온농도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.bod"
|
||||
render={({ field }) => (
|
||||
<FormUi label="생물학적 산소요구량">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.clIonQnt"
|
||||
render={({ field }) => (
|
||||
<FormUi label="염화물">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.smll"
|
||||
render={({ field }) => (
|
||||
<FormUi label="냄새">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.cdtnv"
|
||||
render={({ field }) => (
|
||||
<FormUi label="전기전도도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.crmty"
|
||||
render={({ field }) => (
|
||||
<FormUi label="색도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-span-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="reuseBuilding.0.htdflObj.emsnExitTp"
|
||||
render={({ field }) => (
|
||||
<FormUi label="배출구온도">
|
||||
<Input
|
||||
className={'w-full !text-xl'}
|
||||
placeholder="10"
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
handleCommaNumberChange(e, field.onChange);
|
||||
}}
|
||||
value={formatCommaNumber(field.value)}
|
||||
readOnly={true}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import FeatureClickEventPopup from "../featureClickEventPopup";
|
||||
import { mapService } from "../service";
|
||||
import { MapSearchBar } from "../mapSearchBar";
|
||||
import NewWindowPopup from "../newWindowPopup";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
|
||||
@@ -46,7 +43,7 @@ const Map = () => {
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
const onFeatureSelect = (data: any) => {
|
||||
let map = gisMaps.current;
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
@@ -60,7 +57,7 @@ const Map = () => {
|
||||
select.resetSelectedFeature();
|
||||
|
||||
// 2. data가 OpenLayers Feature 인스턴스인지 확인
|
||||
let foundFeature = null;
|
||||
let foundFeature: any = null;
|
||||
if (
|
||||
data &&
|
||||
typeof data.getGeometry === "function" &&
|
||||
@@ -85,9 +82,9 @@ const Map = () => {
|
||||
|
||||
// 3. 피처 선택
|
||||
if (foundFeature) {
|
||||
let geometry = foundFeature.getGeometry();
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
let extent = geometry.getExtent();
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
@@ -136,7 +133,7 @@ const Map = () => {
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
detailView={(props: any) => (
|
||||
detailView={(props: any) =>(
|
||||
<NewWindowPopup
|
||||
onClose={handlePopupClose} // 메모이제이션된 함수 사용
|
||||
checkedLayers={checkedLayers}
|
||||
@@ -144,7 +141,7 @@ const Map = () => {
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
onFeatureSelect={undefined}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
@@ -152,6 +149,7 @@ const Map = () => {
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
imgVisble={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
// "use client"
|
||||
|
||||
// import { useWrStore } from "@/app/store";
|
||||
// import SearchInputText from "@/components/ui/searchInput/searchInput";
|
||||
// import type { EnvPartSelectDataType, ReturnType, SelectDataType } from "@common/business_components/ui";
|
||||
// import { SelectBox } from "@common/business_components/ui";
|
||||
// import { Button, Input, Label } from "@common/components/ui";
|
||||
// import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
// import { MapSearchFormLeft } from "./mapSearchForm";
|
||||
|
||||
|
||||
// // 건물 검색 타입 정의
|
||||
// interface BuildingInfo {
|
||||
// bdstInfoPk: number;
|
||||
// ctpvCd: number;
|
||||
// sggCd: number;
|
||||
// daddr: string;
|
||||
// bdstNm: string;
|
||||
// frstArchPrmsnDt: string;
|
||||
// archArea: number;
|
||||
// ctpvNm: string | null;
|
||||
// sggNm: string | null;
|
||||
// lat: number | null;
|
||||
// lot: number | null;
|
||||
// regDt: string;
|
||||
// rgtr: string | null;
|
||||
// mdfcnDt: string;
|
||||
// mdfr: string | null;
|
||||
// }
|
||||
|
||||
// type SearchBaseType = {
|
||||
// searchType: string;
|
||||
// selectSidoData?: SelectDataType[] | [];
|
||||
// selectSigunData?: SelectDataType[] | [];
|
||||
// selectRegionData?: SelectDataType[] | [];
|
||||
// selectUnitWaterShedData?: SelectDataType[] | [];
|
||||
// selectMiddleRegionData?: SelectDataType[] | [];
|
||||
// selectEnvPartData?: EnvPartSelectDataType[] | [];
|
||||
// onFeatureSelect: () => void;
|
||||
// btnClass?: string;
|
||||
// ref?: React.RefObject<ReturnType>;
|
||||
// isAdmin?: boolean;
|
||||
// leftClassName?: string;
|
||||
// rightClassName?: string;
|
||||
// showQueryButton?: boolean;
|
||||
// showInitButton?: boolean;
|
||||
// showApplyButton?: boolean;
|
||||
// checkedLayers?: string[];
|
||||
// checkedLayersData?: any[];
|
||||
// }
|
||||
|
||||
// const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
// const YEARS = Array.from({ length: 100 }, (_, i) => `${2025 - i}`);
|
||||
|
||||
// interface SearchModalProps {
|
||||
// open: boolean;
|
||||
// onOpenChange: (open: boolean) => void;
|
||||
// onFeatureSelect: (row: any) => void;
|
||||
// }
|
||||
|
||||
// interface SelectedDataTypeWithSggNm {
|
||||
// text: string;
|
||||
// val: string;
|
||||
// sggNm: string;
|
||||
// }
|
||||
|
||||
// export function MapSearchBar({
|
||||
// searchType,
|
||||
// selectSidoData,
|
||||
// selectSigunData,
|
||||
// selectRegionData,
|
||||
// selectUnitWaterShedData,
|
||||
// selectMiddleRegionData,
|
||||
// selectEnvPartData,
|
||||
// btnClass,
|
||||
// onFeatureSelect,
|
||||
// isAdmin = false,
|
||||
// leftClassName,
|
||||
// rightClassName,
|
||||
// showQueryButton = true,
|
||||
// showInitButton = true,
|
||||
// showApplyButton = true,
|
||||
// checkedLayers = [],
|
||||
// checkedLayersData = [],
|
||||
// }: SearchBaseType) {
|
||||
|
||||
// // 로딩바
|
||||
// const [isLoading, setIsLoading] = useState(false);
|
||||
// // 상세 정보 조회를 위한 상태 추가
|
||||
// const [popupFeature, setPopupFeature] = useState<any>(null);
|
||||
// const [isChHead, setIsChHead] = useState(false);
|
||||
// const ref = useRef<ReturnType>({} as ReturnType);
|
||||
|
||||
// // zustand store에서 건물 검색 함수와 데이터 가져오기
|
||||
// const {
|
||||
// code,
|
||||
// bdst,
|
||||
// findBdst,
|
||||
// wrMap,
|
||||
// buildingSearchRes,
|
||||
// buildingNmSearch,
|
||||
// buildingNmSearchMultiple
|
||||
// } = useWrStore((state) => state);
|
||||
|
||||
// const [selectedBuilding, setSelectedBuilding] = useState<BuildingInfo | null>(null);
|
||||
// const [buildingSearchText, setBuildingSearchText] = useState("");
|
||||
// const [buildingList, setBuildingList] = useState<BuildingInfo[]>([]);
|
||||
|
||||
// // 전체 목록
|
||||
// const [partValue, setPartValue] = useState<string>("A");
|
||||
// const [sidoValue, setSidoValue] = useState<string>("0");
|
||||
// const [sigunValue, setSigunValue] = useState<string>("0");
|
||||
// const [regionValue, setRegionValue] = useState<string>("0");
|
||||
// const [untitWaterShedValue, setUntitWaterShedValue] = useState<string>("0");
|
||||
// const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
|
||||
// const [envPartValue, setEnvPartValue] = useState<string>("0");
|
||||
// const [resetTrigger, setResetTrigger] = useState(false);
|
||||
// const [sidoText, setSidoText] = useState<string>("0");
|
||||
// const [sigunText, setSigunText] = useState<string>("0");
|
||||
// // 공급처 후보지 조회 필터 목록
|
||||
// const [selectedSido, setSelectedSido] = useState<SelectDataType>({ text: "전체", val: '0' });
|
||||
// const [selectedSigun, setSelectedSigun] = useState<SelectDataType>({ text: "전체", val: '0' });
|
||||
// const [selectedFclt, setSelectedFclt] = useState<SelectedDataTypeWithSggNm>({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태
|
||||
// // 전체 시설
|
||||
// let allFclt: SelectedDataTypeWithSggNm[] = [{ text: "전체", val: '0', sggNm: '전체' }];
|
||||
// const [selectedDong, setSelectedDong] = useState<SelectedDataTypeWithSggNm>({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태 // 행정동
|
||||
|
||||
// const filteredSigunData = useMemo(() => {
|
||||
// if (!selectedSido || selectedSido.val === '0' || !selectedSido.val) return [{ text: "전체", val: '0' }];
|
||||
// return [
|
||||
// { text: "전체", val: '0' },
|
||||
// ...code.sggCd.filter((item: { val: string; }) => item.val.substring(0, 2) === selectedSido.val.substring(0, 2))
|
||||
// ];
|
||||
// }, [selectedSido]);
|
||||
|
||||
// const filteredFcltData = useMemo(() => {
|
||||
// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) return [{ text: "전체", val: '0', sggNm: '전체' }];
|
||||
// return [
|
||||
// { text: "전체", val: '0', sggNm: '전체' },
|
||||
// ...allFclt.filter(item => item.sggNm === selectedSido.text)
|
||||
// ];
|
||||
// }, [selectedSigun]);
|
||||
|
||||
// const filteredDong = useMemo(() => {
|
||||
// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) return [{ text: "전체", val: '0', sggNm: '전체' }];
|
||||
// return [
|
||||
// { text: "전체", val: '0', sggNm: '전체' },
|
||||
// // ...code.dong.filter(item => item.sggNm === selectedSido.text)
|
||||
// ];
|
||||
// }, [selectedSigun]);
|
||||
|
||||
// // 조회 함수
|
||||
// const queryVal = () => {
|
||||
// // 46 : 하수처리시설
|
||||
// // 47 : 하수재이용시설
|
||||
// // 48 : 빗물이용시설
|
||||
// // 49 : 중수도시설
|
||||
// // 50 : 온배수시설
|
||||
// console.log("조회버튼 클릭: ", checkedLayers);
|
||||
// if (checkedLayers && checkedLayers.includes('48')) {
|
||||
// // searchBuildings
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 초기화 함수
|
||||
// const initVal = () => {
|
||||
// setPartValue("A");
|
||||
// setSidoValue("0");
|
||||
// setSigunValue("0");
|
||||
// setRegionValue("0");
|
||||
// setUntitWaterShedValue("0");
|
||||
// setMiddleRegionValue("0");
|
||||
// setEnvPartValue("0");
|
||||
// setSidoText("전체");
|
||||
// setSigunText("전체");
|
||||
// setSelectedSido({ text: "전체", val: '0' });
|
||||
// setSelectedSigun({ text: "전체", val: '0' });
|
||||
// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' });
|
||||
// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' });
|
||||
// setResetTrigger(true);
|
||||
// };
|
||||
|
||||
// // 적용 버튼
|
||||
// const applyVal = () => {
|
||||
// onFeatureSelect();
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// if (resetTrigger) {
|
||||
// setResetTrigger(false);
|
||||
// }
|
||||
// }, [resetTrigger]);
|
||||
|
||||
// // 시설 조회
|
||||
// useEffect(() => {
|
||||
// const fetchData = async () => {
|
||||
// setIsLoading(true);
|
||||
// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) {
|
||||
// allFclt = ([{ text: "전체", val: '0', sggNm: '전체' }]);
|
||||
// setIsLoading(false);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// const queryParamsFacility = new URLSearchParams({
|
||||
// lyrTypeNm: 'tb_il_swage_prcs_fclt',
|
||||
// page: '0',
|
||||
// size: '100000',
|
||||
// sort: 'string',
|
||||
// });
|
||||
// const cqlFiltersFacility: string[] = [];
|
||||
// if (selectedSigun.text && selectedSigun.val !== '0') {
|
||||
// cqlFiltersFacility.push(`sgg_nm ILIKE '%${selectedSigun.text}%'`);
|
||||
// }
|
||||
// if (cqlFiltersFacility.length > 0) {
|
||||
// queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and '));
|
||||
// }
|
||||
// console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`);
|
||||
|
||||
// const facilityRes = await fetch(`${API_URL}/v1/fclts?${queryParamsFacility.toString()}`, {
|
||||
// method: "GET",
|
||||
// headers: { accept: "application/json" }
|
||||
// }).then(res => res.json());
|
||||
|
||||
// const facilities = (facilityRes.data || []).map((item: any) => ({
|
||||
// text: item.swtrmtFcltNm,
|
||||
// val: item.fid,
|
||||
// sggNm: item.sggNm
|
||||
// }));
|
||||
// allFclt = (facilities);
|
||||
|
||||
// } catch (error) {
|
||||
// console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
|
||||
// allFclt = ([{ text: "전체", val: '0', sggNm: '전체' }]);
|
||||
// } finally {
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// fetchData();
|
||||
// }, [selectedSigun]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (ref && ref.current) {
|
||||
// const customRef = ref.current;
|
||||
// customRef.partValue = partValue;
|
||||
// customRef.sidoValue = sidoValue;
|
||||
// customRef.sigunValue = sigunValue;
|
||||
// customRef.regionValue = regionValue;
|
||||
// customRef.unitWaterShedValue = untitWaterShedValue;
|
||||
// customRef.middleRegionValue = middleRegionValue;
|
||||
// customRef.envPartValue = envPartValue;
|
||||
// customRef.sidoText = selectedSido.text;
|
||||
// customRef.sigunText = selectedSigun.text;
|
||||
// }
|
||||
// }, [partValue, sidoValue, sigunValue, regionValue, untitWaterShedValue, middleRegionValue, envPartValue, ref]);
|
||||
|
||||
// // buildingSearchText가 변경될 때 API 호출
|
||||
// useEffect(() => {
|
||||
// if (buildingSearchText && buildingSearchText.length > 1) {
|
||||
// if (checkedLayers && checkedLayers.length > 0) {
|
||||
// // 다중 레이어 검색
|
||||
// // buildingNmSearchMultiple(checkedLayers, selectedSigun.val || "", buildingSearchText);
|
||||
// buildingNmSearch("RNB", selectedSigun.val, buildingSearchText);
|
||||
// } else {
|
||||
// // 단일 레이어 검색
|
||||
// // buildingNmSearch(checkedLayers[0], selectedSigun.val, buildingSearchText);
|
||||
// }
|
||||
// } else {
|
||||
// // 검색 텍스트가 짧으면 리스트 초기화
|
||||
// if (checkedLayers && checkedLayers.length > 0) {
|
||||
// // buildingNmSearchMultiple(checkedLayers, selectedSigun.val || "", "");
|
||||
// } else {
|
||||
// buildingNmSearch("RNB", selectedSigun.val || "", "");
|
||||
// }
|
||||
// }
|
||||
// }, [buildingSearchText]);
|
||||
|
||||
// return (
|
||||
// <div className="absolute left-1/2 -translate-x-1/2 bg-white flex items-center justify-center p-4 rounded-md shadow-lg w-auto min-w-fit">
|
||||
// <div className={leftClassName ? leftClassName : `flex items-center justify-center space-x-2 `}>
|
||||
// <div className="flex gap-8 items-center">
|
||||
// {searchType == "공급처 후보지 조회" && (
|
||||
// <>
|
||||
// <SelectBox
|
||||
// className="flex items-center"
|
||||
// label={'시도'}
|
||||
// selectArray={code.cityCd}
|
||||
// onSelectValue={(val) => {
|
||||
// const selected = code.cityCd.find((item: { val: string | number; }) => (item.val === val));
|
||||
// setSelectedSido(selected || { text: "전체", val: '0' });
|
||||
// setSelectedSigun({ text: "전체", val: '0' });
|
||||
// }}
|
||||
// value={selectedSido.val}
|
||||
// selectClass={'w-32'}
|
||||
// />
|
||||
// <SelectBox
|
||||
// className="flex items-center"
|
||||
// label={'시군구'}
|
||||
// selectArray={filteredSigunData}
|
||||
// onSelectValue={(val) => {
|
||||
// const selected = filteredSigunData.find(item => (item.val === val));
|
||||
// setSelectedSigun(selected || { text: "전체", val: '0' });
|
||||
// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' });
|
||||
// }}
|
||||
// value={selectedSigun.val}
|
||||
// selectClass={'w-32'}
|
||||
// />
|
||||
// <div className="flex items-center">
|
||||
// <Label className="w-20 text-xl text-gray-600">시설명</Label>
|
||||
// <div className="w-64">
|
||||
// <SearchInputText
|
||||
// label="시설명"
|
||||
// initValue={buildingSearchText}
|
||||
// setValue={(searchNm?: string) => {
|
||||
// setBuildingSearchText(searchNm || "");
|
||||
// }}
|
||||
// setRtnData={function (rtnData: any): void {
|
||||
// // 선택된 건물 데이터 처리
|
||||
// // if (rtnData && rtnData.bdstInfoPk) {
|
||||
// // setSelectedBuilding(rtnData);
|
||||
// // setBuildingSearchText(rtnData.bdstNm || "");
|
||||
// // }
|
||||
// }}
|
||||
// searchList={buildingSearchRes || []}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
// </>
|
||||
// )}
|
||||
// {searchType == "우리동네가뭄지도보기" && (
|
||||
// <>
|
||||
// <SelectBox
|
||||
// className="flex items-center"
|
||||
// label={'시도'}
|
||||
// selectArray={code.cityCd}
|
||||
// onSelectValue={(val) => {
|
||||
// const selected = code.cityCd.find((item: { val: string | number; }) => (item.val === val));
|
||||
// setSelectedSido(selected || { text: "전체", val: '0' });
|
||||
// setSelectedSigun({ text: "전체", val: '0' });
|
||||
// }}
|
||||
// value={selectedSido.val}
|
||||
// selectClass={'w-44'}
|
||||
// />
|
||||
// <SelectBox
|
||||
// className="flex items-center"
|
||||
// label={'시군구'}
|
||||
// selectArray={filteredSigunData}
|
||||
// onSelectValue={(val) => {
|
||||
// const selected = filteredSigunData.find(item => (item.val === val));
|
||||
// setSelectedSigun(selected || { text: "전체", val: '0' });
|
||||
// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' });
|
||||
// }}
|
||||
// value={selectedSigun.val}
|
||||
// selectClass={'w-32'}
|
||||
// />
|
||||
// <SelectBox
|
||||
// className="flex items-center mr-8"
|
||||
// label={'행정동'}
|
||||
// selectArray={filteredDong}
|
||||
// onSelectValue={(val) => {
|
||||
// const selected = filteredDong.find(item => (item.val === val));
|
||||
// setSelectedDong(selected || { text: "전체", val: '0', sggNm: '전체' });
|
||||
// }}
|
||||
// value={selectedDong.val}
|
||||
// selectClass={'w-32'}
|
||||
// />
|
||||
// </>
|
||||
// )}
|
||||
// {searchType == "지도보기" && (
|
||||
// <div className="flex items-center gap-2 w-[800px]">
|
||||
// <div className="flex min-w-0"> {/* SearchFormLeft 래퍼 */}
|
||||
// <MapSearchFormLeft
|
||||
// isShowPart={true}
|
||||
// selectSidoData={code.cityCd}
|
||||
// selectSigunData={filteredSigunData}
|
||||
// selectRegionData={code.rvbsnCd}
|
||||
// selectUnitWaterShedData={selectUnitWaterShedData}
|
||||
// selectMiddleRegionData={selectMiddleRegionData}
|
||||
// selectEnvPartData={selectEnvPartData}
|
||||
// setRtnPartValue={setPartValue}
|
||||
// setRtnSidoValue={setSidoValue}
|
||||
// setRtnSigunValue={setSigunValue}
|
||||
// setRtnRegionValue={setRegionValue}
|
||||
// setRtnUnitWaterShedValue={setUntitWaterShedValue}
|
||||
// setRtnMiddleRegionValue={setMiddleRegionValue}
|
||||
// setRtnEnvPartValue={setEnvPartValue}
|
||||
// setRtnEnvPartSidoValue={setSidoValue}
|
||||
// setRtnEnvPartSigunValue={setSigunValue}
|
||||
// setRtnSidoText={setSidoText}
|
||||
// setRtnSigunText={setSigunText}
|
||||
// resetTrigger={resetTrigger}
|
||||
// isAdmin={isAdmin}
|
||||
// children={undefined}
|
||||
// />
|
||||
// </div>
|
||||
// <Label className="w-auto text-xl text-gray-600 whitespace-nowrap">시설명</Label>
|
||||
// <div className="w-44">
|
||||
// <Input className="mr-4" />
|
||||
// </div>
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// {/* 버튼 부분 */}
|
||||
// <div className="flex items-center justify-center gap-4">
|
||||
// {showQueryButton && <Button className={btnClass} onClick={queryVal}>조회</Button>}
|
||||
// {showInitButton && <Button className={btnClass} onClick={initVal}>초기화</Button>}
|
||||
// {showApplyButton && <Button className={btnClass} onClick={applyVal}>적용</Button>}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user