Merge branch 'iipbs/common/main' into iipbs/sn/ii

This commit is contained in:
swcho
2025-07-16 10:04:51 +09:00
245 changed files with 7714 additions and 8817 deletions
+8
View File
@@ -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> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>제목</TableHead> <TableHead data-required>제목</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -120,7 +120,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
</TableRow> </TableRow>
{!userInfo?.userUnqId && ( {!userInfo?.userUnqId && (
<TableRow> <TableRow>
<TableHead>비밀번호</TableHead> <TableHead data-required>비밀번호</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -44,7 +44,9 @@ function PostPage({ bbsId }: { bbsId: string }) {
const thumbRefs = useRef<Map<number, string>>(new Map()); const thumbRefs = useRef<Map<number, string>>(new Map());
const [tableSort, setTableSort] = useState(""); 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", keywordType: "title",
keyword: "", keyword: "",
}); });
@@ -58,7 +60,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo], queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, bbsInfo],
queryFn: () => queryFn: () =>
PostApi.getPostList(parseInt(bbsId), { PostApi.getPostList(parseInt(bbsId), {
...postParams, ...searchParams,
upndFixYn: bbsInfo?.upndFixUseYn, upndFixYn: bbsInfo?.upndFixUseYn,
}), }),
select: (data) => { select: (data) => {
@@ -79,10 +81,16 @@ function PostPage({ bbsId }: { bbsId: string }) {
isLoading, isLoading,
refetch, refetch,
} = useQuery({ } = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pagination, tableSort], queryKey: [
ENDPOINT.BOARD_SERVICE.POSTS_USER,
bbsId,
searchParams,
pagination,
tableSort,
],
queryFn: () => queryFn: () =>
PostApi.getPostList(parseInt(bbsId), { PostApi.getPostList(parseInt(bbsId), {
...postParams, ...searchParams,
ansUseYn: bbsInfo?.ansUseYn, ansUseYn: bbsInfo?.ansUseYn,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
@@ -128,7 +136,29 @@ function PostPage({ bbsId }: { bbsId: string }) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); 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) => { const handleRowClick = (row: Post) => {
@@ -141,12 +171,7 @@ function PostPage({ bbsId }: { bbsId: string }) {
<div className="card card-border"> <div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2"> <form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2 text-label">검색</Label> <Label className="mr-2 text-label">검색</Label>
<Select <Select value={keywordType} onValueChange={setKeywordType}>
value={postParams.keywordType}
onValueChange={(keywordType) => {
setPostParams({ ...postParams, keywordType });
}}
>
<SelectTrigger className="h-[3.2rem] w-[10rem]"> <SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
</SelectTrigger> </SelectTrigger>
@@ -157,15 +182,21 @@ function PostPage({ bbsId }: { bbsId: string }) {
</SelectContent> </SelectContent>
</Select> </Select>
<Input <Input
value={postParams.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setPostParams({ ...postParams, keyword: e.target.value })
}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+25
View File
@@ -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 { export class ApiLogApiService implements ApiLogService {
public async getApiLogList(params: ApiLogParams): Promise<ApiLogList> { public async getApiLogList(params: ApiLogParams): Promise<ApiLogList> {
const response = await client.get<ApiLogList>( const response = await client.get<ApiLogList>(
`${ENDPOINT.USER_SERVICE.APILOG}`, `${ENDPOINT.CMS_SERVICE.APILOG}`,
{ params }, { params },
); );
return response.data; return response.data;
} }
} }
export const ApiLogApi = new ApiLogApiService(); export const ApiLogApi = new ApiLogApiService();
@@ -2,8 +2,9 @@ import type { PageableList, PageableParams } from "@/shared/model";
export interface ApiLogParams extends PageableParams { export interface ApiLogParams extends PageableParams {
siteId?: string; siteId?: string;
userId?: string; userUnqId?: string;
userNm?: string; userNm?: string;
userId?: string;
apiHttpPmhd?: string; apiHttpPmhd?: string;
dmndUrl?: string; dmndUrl?: string;
ipAddr?: string; ipAddr?: string;
@@ -85,6 +85,15 @@ export class ContentsApiService implements ContentsService {
`${ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS}/${contsSn}/${contsVer}/approve`, `${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(); export const ContentsApi = new ContentsApiService();
@@ -97,4 +97,5 @@ export interface ContentsService {
deleteContents(contsSn: number, contsVer: number): Promise<void>; deleteContents(contsSn: number, contsVer: number): Promise<void>;
requestApprove(contsSn: number, contsVer: number): Promise<void>; requestApprove(contsSn: number, contsVer: number): Promise<void>;
approveContents(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 { export interface GroupUserSearchParams extends PageableParams {
keyword?: string; keyword?: string;
userNm?: string;
} }
// 그룹 사용자 목록 응답 인터페이스 // 그룹 사용자 목록 응답 인터페이스
+1
View File
@@ -3,4 +3,5 @@ export {
groupFormSchema, groupFormSchema,
type GroupFormData, type GroupFormData,
type GroupListResponseDto, type GroupListResponseDto,
type GroupSearchParams,
} from "./model/group-interface"; } from "./model/group-interface";
@@ -56,7 +56,10 @@ export const groupFormSchema = z.object({
groupNm: z.string().min(1, { message: "그룹명을 입력해주세요." }), groupNm: z.string().min(1, { message: "그룹명을 입력해주세요." }),
groupSeCd: z.string().min(1, { message: "그룹구분을 선택해주세요." }), groupSeCd: z.string().min(1, { message: "그룹구분을 선택해주세요." }),
groupExpln: z.string().optional(), 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를 선택해주세요." }), authrtId: z.string().min(1, { message: "권한ID를 선택해주세요." }),
siteId: z.string().optional(), siteId: z.string().optional(),
mngrYn: z.string().default("N"), mngrYn: z.string().default("N"),
@@ -74,7 +74,7 @@ function ManagementAuthorityDialog({ menuCd }: ManagementAuthorityDialogProps) {
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="gap-3" disabled={!menuCd}> <Button className="gap-3" disabled={!menuCd}>
<i className="diveicon di-personal-information" /> <i className="diveicon di-key" />
권한관리 권한관리
</Button> </Button>
</DialogTrigger> </DialogTrigger>
+57 -1
View File
@@ -1,6 +1,12 @@
import { client } from "@/shared/api"; import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config"; 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 { export class MenuApiService implements MenuService {
async getMenuRoles(siteId: string): Promise<MenuItem[]> { async getMenuRoles(siteId: string): Promise<MenuItem[]> {
@@ -9,6 +15,56 @@ export class MenuApiService implements MenuService {
); );
return response.data; 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(); export const MenuApi = new MenuApiService();
+8 -1
View File
@@ -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 { 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 { export interface MenuItem {
menuCd: number; menuCd: number;
menuKornNm: string; menuKornNm: string;
@@ -18,6 +31,65 @@ export interface MenuItem {
requiredUrlPath: boolean; 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 { export interface MenuService {
getMenuRoles(siteId: string): Promise<MenuItem[]>; 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[]>;
} }
+42 -16
View File
@@ -38,10 +38,14 @@ function AdminPage() {
const alert = useAlertStore((state) => state.setMessage); const alert = useAlertStore((state) => state.setMessage);
const { confirm } = useConfirm(); 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: "", keywordType: "",
keyword: "", keyword: "",
grpId: "", grpId: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -66,10 +70,10 @@ function AdminPage() {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination], queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParams, pagination],
queryFn: () => queryFn: () =>
UserApi.findPageAdminUser({ UserApi.findPageAdminUser({
...adminQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -123,7 +127,28 @@ function AdminPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); 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 () => { const handleDeleteAdmins = async () => {
@@ -161,11 +186,7 @@ function AdminPage() {
<form onSubmit={handleSubmit} className="flex items-center gap-6 p-2"> <form onSubmit={handleSubmit} className="flex items-center gap-6 p-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label className="mr-2 text-label">관리자구분</Label> <Label className="mr-2 text-label">관리자구분</Label>
<Select <Select value={grpId} onValueChange={(value) => setGrpId(value)}>
onValueChange={(value) =>
setAdminQuery({ ...adminQuery, grpId: value })
}
>
<SelectTrigger className="h-[3.2rem] w-[16rem] !text-[1.3rem]"> <SelectTrigger className="h-[3.2rem] w-[16rem] !text-[1.3rem]">
<SelectValue placeholder="그룹 선택" /> <SelectValue placeholder="그룹 선택" />
</SelectTrigger> </SelectTrigger>
@@ -183,9 +204,8 @@ function AdminPage() {
관리자 관리자
</Label> </Label>
<Select <Select
onValueChange={(value) => value={keywordType}
setAdminQuery({ ...adminQuery, keywordType: value }) onValueChange={(value) => setKeywordType(value)}
}
> >
<SelectTrigger className="h-[3.2rem] w-[10rem] !text-[1.3rem]"> <SelectTrigger className="h-[3.2rem] w-[10rem] !text-[1.3rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
@@ -197,16 +217,22 @@ function AdminPage() {
</Select> </Select>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={adminQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setAdminQuery({ ...adminQuery, keyword: e.target.value })
}
placeholder="검색어 입력" placeholder="검색어 입력"
/> />
</div> </div>
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+48 -37
View File
@@ -26,36 +26,32 @@ type DateRange = {
to?: Date | undefined; to?: Date | undefined;
}; };
function ApiLogPage({}) { function ApiLogPage() {
// 1주일 전부터 오늘까지 기본 날짜 설정
const defaultDateRange: DateRange = { const defaultDateRange: DateRange = {
from: dayjs().subtract(7, "day").toDate(), from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(), to: dayjs().toDate(),
}; };
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
const [loginlogPostQuery, setLoginlogPostQuery] = useState<ApiLogParams>({ const [userId, setUserId] = useState("");
const [apiHttpPmhd, setApiHttpPmhd] = useState("");
const [searchParams, setSearchParams] = useState<ApiLogParams>({
userId: "", userId: "",
siteId: "",
apiHttpPmhd: "", apiHttpPmhd: "",
dmndUrl: "",
ipAddr: "",
dmndDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"), dmndDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
dmndDtEnd: dayjs().format("YYYY-MM-DD"), dmndDtEnd: dayjs().format("YYYY-MM-DD"),
sort: "lgnDt,asc", sort: "regDt,desc",
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination], queryKey: [ENDPOINT.CMS_SERVICE.APILOG, searchParams, pagination],
queryFn: () => queryFn: () =>
ApiLogApi.getApiLogList({ ApiLogApi.getApiLogList({
...loginlogPostQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -81,13 +77,31 @@ function ApiLogPage({}) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); 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 = const handleReset = () => {
(key: keyof ApiLogParams) => (value: unknown) => { setUserId("");
setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); 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) => { const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange | undefined; let presetDateRange: DateRange | undefined;
@@ -113,19 +127,7 @@ function ApiLogPage({}) {
default: default:
presetDateRange = undefined; presetDateRange = undefined;
} }
handleDateRangeChange(presetDateRange); setDateRange(presetDateRange || defaultDateRange);
};
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"),
});
}; };
return ( return (
@@ -135,7 +137,7 @@ function ApiLogPage({}) {
<Label className="mr-2">조회기간</Label> <Label className="mr-2">조회기간</Label>
<DateRangePicker <DateRangePicker
value={dateRange} value={dateRange}
onValueChange={handleDateRangeChange} onValueChange={(value) => setDateRange(value || defaultDateRange)}
> >
<div className="card card-border flex items-center gap-2"> <div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}> <Select onValueChange={handlePresetDateRange}>
@@ -152,14 +154,15 @@ function ApiLogPage({}) {
</DateRangePicker> </DateRangePicker>
<Label className="ml-4 mr-2 text-label">사용자ID</Label> <Label className="ml-4 mr-2 text-label">사용자ID</Label>
<Input <Input
value={userId}
onChange={(e) => setUserId(e.target.value)}
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" 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> <Label className="ml-4 mr-2 text-label">상태</Label>
<Select onValueChange={handleLoginlogParamsChange("apiHttpPmhd")}> <Select
value={apiHttpPmhd}
onValueChange={(value) => setApiHttpPmhd(value)}
>
<SelectTrigger className="w-[10rem]"> <SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
</SelectTrigger> </SelectTrigger>
@@ -173,12 +176,20 @@ function ApiLogPage({}) {
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
<div className="card-header"> <div className="card-header">
<div className="flex w-full items-center justify-between"> <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"> <div className="flex gap-2">
<Select <Select
value={table.getState().pagination.pageSize.toString()} value={table.getState().pagination.pageSize.toString()}
+4 -4
View File
@@ -84,7 +84,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>배너명</TableHead> <TableHead data-required>배너명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -105,7 +105,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>배너구분</TableHead> <TableHead data-required>배너구분</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -210,7 +210,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>링크 열기</TableHead> <TableHead data-required>링크 열기</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -260,7 +260,7 @@ function BannerForm({ form, bnrSn, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용여부</TableHead> <TableHead data-required>사용여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+34 -9
View File
@@ -28,17 +28,21 @@ function BannerPage() {
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]); 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>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 5, pageSize: 10,
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, pagination], queryKey: [ENDPOINT.CMS_SERVICE.BANNERS, searchParams, pagination],
queryFn: () => queryFn: () =>
BannerApi.getBannerList({ BannerApi.getBannerList({
...bannerQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -46,7 +50,22 @@ function BannerPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
bnrTtl,
});
};
const handleReset = () => {
setBnrTtl("");
setSearchParams({
...searchParams,
bnrTtl: "",
});
}; };
const handleRowClick = (row: Banner) => { const handleRowClick = (row: Banner) => {
@@ -107,14 +126,20 @@ function BannerPage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={bannerQuery.bnrTtl} value={bnrTtl}
onChange={(e) => onChange={(e) => setBnrTtl(e.target.value)}
setBannerQuery({ ...bannerQuery, bnrTtl: e.target.value })
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+14 -14
View File
@@ -54,7 +54,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>게시판명</TableHead> <TableHead data-required>게시판명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -75,7 +75,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>게시판 설명</TableHead> <TableHead data-required>게시판 설명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -96,7 +96,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>게시판 유형</TableHead> <TableHead data-required>게시판 유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -116,7 +116,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>참조 사용 여부</TableHead> <TableHead data-required>참조 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -146,7 +146,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>첨부파일 사용 여부</TableHead> <TableHead data-required>첨부파일 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -236,7 +236,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>답글 사용 여부</TableHead> <TableHead data-required>답글 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -295,7 +295,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>댓글 사용 여부</TableHead> <TableHead data-required>댓글 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -329,7 +329,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>게시물평가 사용 여부</TableHead> <TableHead data-required>게시물평가 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -359,7 +359,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>팝업 사용 여부</TableHead> <TableHead data-required>팝업 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -389,7 +389,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>SMS알림 사용 여부</TableHead> <TableHead data-required>SMS알림 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -419,7 +419,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>상단고정 사용 여부</TableHead> <TableHead data-required>상단고정 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -449,7 +449,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>게시기간 사용 여부</TableHead> <TableHead data-required>게시기간 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -479,7 +479,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>개인정보 표시 여부</TableHead> <TableHead data-required>개인정보 표시 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -508,7 +508,7 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용 여부</TableHead> <TableHead data-required>사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+37 -12
View File
@@ -29,9 +29,12 @@ function BbsPage() {
const router = useRouter(); const router = useRouter();
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [boardParams, setBoardParams] = useState<BoardParams>({ const [bbsTypeCd, setBbsTypeCd] = useState("");
const [bbsNm, setBbsNm] = useState("");
const [searchParams, setSearchParams] = useState<BoardParams>({
bbsTypeCd: "", bbsTypeCd: "",
bbsNm: "", bbsNm: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -39,10 +42,10 @@ function BbsPage() {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.BBS, pagination], queryKey: [ENDPOINT.BOARD_SERVICE.BBS, searchParams, pagination],
queryFn: () => queryFn: () =>
BoardApi.getBoardList({ BoardApi.getBoardList({
...boardParams, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -50,7 +53,25 @@ function BbsPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
bbsTypeCd,
bbsNm,
});
};
const handleReset = () => {
setBbsTypeCd("");
setBbsNm("");
setSearchParams({
...searchParams,
bbsTypeCd: "",
bbsNm: "",
});
}; };
const handleRowClick = (row: Board) => { const handleRowClick = (row: Board) => {
@@ -86,10 +107,8 @@ function BbsPage() {
</Label> </Label>
<CodeSelect <CodeSelect
upCd="BBSTYPE" upCd="BBSTYPE"
value={boardParams.bbsTypeCd} value={bbsTypeCd}
onValueChange={(value) => onValueChange={(value) => setBbsTypeCd(value)}
setBoardParams({ ...boardParams, bbsTypeCd: value })
}
/> />
<Label htmlFor="bbsNm" className="ml-4 mr-2 text-label"> <Label htmlFor="bbsNm" className="ml-4 mr-2 text-label">
게시판명 게시판명
@@ -97,14 +116,20 @@ function BbsPage() {
<Input <Input
id="bbsNm" id="bbsNm"
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={boardParams.bbsNm} value={bbsNm}
onChange={(e) => onChange={(e) => setBbsNm(e.target.value)}
setBoardParams({ ...boardParams, bbsNm: e.target.value })
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -5,56 +5,62 @@ import type { LwpoCode } from "@/entities/code";
import { LwpoCodeManagementDialog } from "../ui/lwpocode-dialog"; import { LwpoCodeManagementDialog } from "../ui/lwpocode-dialog";
import { useState } from "react"; import { useState } from "react";
export const columns: ColumnDef<LwpoCode>[] = [ // columns를 함수로 변경하여 onAfterSave를 주입받음
{ export function getColumns(onAfterSave: () => void) : ColumnDef<LwpoCode>[] {
id: "no", return [
header: "NO", {
cell: ({ row, table }) => { id: "no",
const totalRows = table.getRowCount(); header: "NO",
const { pageIndex, pageSize } = table.getState().pagination; cell: ({ row, table }) => {
return totalRows - (pageIndex * pageSize + row.index); const totalRows = table.getRowCount();
const { pageIndex, pageSize } = table.getState().pagination;
return totalRows - (pageIndex * pageSize + row.index);
},
}, },
}, {
{ accessorKey: "lwpoCd",
accessorKey: "lwpoCd", header: "하위코드",
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: "lwpoCdNm",
id: "actions", header: "하위코드명",
header: "관리", },
cell: ({ row }) => ( {
<div onClick={(e) => e.stopPropagation()}> accessorKey: "useYn",
<LwpoCodeManageButton header: "사용 여부",
lwpoCd={row.original.lwpoCd} },
upCd={row.original.upCd} {
/> accessorKey: "regDt",
</div> 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 = ({ const LwpoCodeManageButton = ({
lwpoCd, lwpoCd,
upCd, upCd,
onAfterSave,
}: { }: {
lwpoCd: string; lwpoCd: string;
upCd: string; upCd: string;
onAfterSave: () => void;
}) => { }) => {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
return ( return (
@@ -63,6 +69,7 @@ const LwpoCodeManageButton = ({
upCd={upCd} upCd={upCd}
open={dialogOpen} open={dialogOpen}
onOpenChange={setDialogOpen} onOpenChange={setDialogOpen}
onAfterSave={onAfterSave}
> >
<Button size="sm">관리</Button> <Button size="sm">관리</Button>
</LwpoCodeManagementDialog> </LwpoCodeManagementDialog>
+64 -18
View File
@@ -1,7 +1,7 @@
import { memo } from "react"; import { memo } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; 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 { useConfirm } from "@frontend-opensource/use-react-hooks";
import { import {
Button, Button,
@@ -38,6 +38,7 @@ type ManagementLwpoCodeDialogProps = {
children?: React.ReactNode; children?: React.ReactNode;
open?: boolean; open?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
onAfterSave?: () => void; // 콜백 추가
}; };
const LwpoCodeManagementDialog = memo( const LwpoCodeManagementDialog = memo(
@@ -47,17 +48,18 @@ const LwpoCodeManagementDialog = memo(
children, children,
open, open,
onOpenChange, onOpenChange,
onAfterSave, // 콜백 사용
...props ...props
}: ManagementLwpoCodeDialogProps) => { }: ManagementLwpoCodeDialogProps) => {
const isRegistration = !lwpoCd; const isRegistration = !lwpoCd;
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage); const alert = useAlertStore((state) => state.setMessage);
const { data: detailData } = useQuery({ const { data: detailData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, lwpoCd], queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, lwpoCd],
queryFn: () => LwpoCodeApi.lwpoCodeDetail(upCd!, lwpoCd!), queryFn: () => LwpoCodeApi.lwpoCodeDetail(upCd!, lwpoCd!),
enabled: !!upCd && !isRegistration, enabled: !!upCd && !isRegistration && open,
}); });
const { data: upCodeData } = useQuery({ const { data: upCodeData } = useQuery({
@@ -69,7 +71,13 @@ const LwpoCodeManagementDialog = memo(
const { mutateAsync: createLwpoCode } = useMutationWrapper({ const { mutateAsync: createLwpoCode } = useMutationWrapper({
mutationFn: (data: LwpoCodeFormData) => LwpoCodeApi.createLwpoCode(data), mutationFn: (data: LwpoCodeFormData) => LwpoCodeApi.createLwpoCode(data),
onSuccess: () => { 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) => mutationFn: (data: LwpoCodeFormData) =>
LwpoCodeApi.updateLwpoCode(upCd!, lwpoCd!, data), LwpoCodeApi.updateLwpoCode(upCd!, lwpoCd!, data),
onSuccess: () => { onSuccess: () => {
alert("수정되었습니다.", () => onOpenChange?.(false)); alert("수정되었습니다.", () => {
onOpenChange?.(false);
onAfterSave?.();
});
}, },
}); });
@@ -96,15 +107,38 @@ const LwpoCodeManagementDialog = memo(
}, },
}); });
const onSubmit = const handleRegister = async () => {
(callback: (data: LwpoCodeFormData) => void) => if (!upCd) {
async (data: LwpoCodeFormData) => { alert("상위코드가 없습니다.");
const confirmMessage = isRegistration return;
? "등록하시겠습니까?" }
: "수정하시겠습니까?"; const confirmMessage = "등록하시겠습니까?";
if (await confirm(confirmMessage)) {
if (await confirm(confirmMessage)) callback(data); 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 ( return (
<Dialog open={open} onOpenChange={onOpenChange} {...props}> <Dialog open={open} onOpenChange={onOpenChange} {...props}>
@@ -117,9 +151,14 @@ const LwpoCodeManagementDialog = memo(
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit( onSubmit={(e) => {
onSubmit(isRegistration ? createLwpoCode : updateLwpoCode), e.preventDefault();
)} if (isRegistration) {
handleRegister();
} else {
handleSave();
}
}}
className="space-y-4" className="space-y-4"
> >
<Table variant="secondary"> <Table variant="secondary">
@@ -251,6 +290,9 @@ const LwpoCodeManagementDialog = memo(
{...field} {...field}
type="number" type="number"
value={field.value ?? 0} value={field.value ?? 0}
onChange={(e) => {
field.onChange(parseInt(e.target.value));
}}
className="!text-[1.3rem]" className="!text-[1.3rem]"
placeholder="정렬순서를 입력해주세요" placeholder="정렬순서를 입력해주세요"
/> />
@@ -292,7 +334,11 @@ const LwpoCodeManagementDialog = memo(
</TableBody> </TableBody>
</Table> </Table>
<DialogFooter className="!justify-center"> <DialogFooter className="!justify-center">
<Button size="lg" type="submit"> <Button
size="lg"
type="submit"
onClick={isRegistration ? handleRegister : handleSave}
>
{isRegistration ? "등록" : "저장"} {isRegistration ? "등록" : "저장"}
</Button> </Button>
<DialogClose asChild> <DialogClose asChild>
+40 -12
View File
@@ -5,7 +5,7 @@ import { Button, Input, Label } from "@common/components";
import { LwpoCodeApi, type LwpoCodeParams } from "@/entities/code"; import { LwpoCodeApi, type LwpoCodeParams } from "@/entities/code";
import { ENDPOINT } from "@/shared/config"; import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable } from "@/shared/lib"; 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"; import { LwpoCodeManagementDialog } from "./lwpocode-dialog";
type LwpoCodeSectionProps = { type LwpoCodeSectionProps = {
@@ -15,8 +15,10 @@ type LwpoCodeSectionProps = {
function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) { function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
const alert = useAlertStore((state) => state.setMessage); const alert = useAlertStore((state) => state.setMessage);
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [lwpoCodeQuery, setLwpoCodeQuery] = useState<LwpoCodeParams>({ const [lwpoCdNm, setLwpoCdNm] = useState("");
const [searchParams, setSearchParams] = useState<LwpoCodeParams>({
lwpoCdNm: "", lwpoCdNm: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -24,10 +26,10 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, pagination], queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, searchParams, pagination],
queryFn: () => queryFn: () =>
LwpoCodeApi.getLwpoCodeList({ LwpoCodeApi.getLwpoCodeList({
...lwpoCodeQuery, ...searchParams,
upCd, upCd,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
@@ -36,6 +38,12 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
placeholderData: keepPreviousData, placeholderData: keepPreviousData,
}); });
// 등록/수정 후 목록 재조회 콜백
const handleAfterSave = () => {
query.refetch();
};
const columns = getColumns(handleAfterSave);
const { mutateAsync: codeRefreshAll } = useMutation({ const { mutateAsync: codeRefreshAll } = useMutation({
mutationFn: LwpoCodeApi.codeRefreshAll, mutationFn: LwpoCodeApi.codeRefreshAll,
onSuccess: () => { onSuccess: () => {
@@ -59,7 +67,22 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
lwpoCdNm,
});
};
const handleReset = () => {
setLwpoCdNm("");
setSearchParams({
...searchParams,
lwpoCdNm: "",
});
}; };
useEffect(() => { useEffect(() => {
@@ -75,17 +98,20 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={lwpoCodeQuery.lwpoCdNm} value={lwpoCdNm}
onChange={(e) => onChange={(e) => setLwpoCdNm(e.target.value)}
setLwpoCodeQuery({
...lwpoCodeQuery,
lwpoCdNm: e.target.value,
})
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -96,8 +122,10 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) {
코드갱신 코드갱신
</Button> </Button>
<LwpoCodeManagementDialog <LwpoCodeManagementDialog
upCd={upCd}
open={dialogOpen} open={dialogOpen}
onOpenChange={setDialogOpen} onOpenChange={setDialogOpen}
onAfterSave={handleAfterSave}
> >
<Button>등록</Button> <Button>등록</Button>
</LwpoCodeManagementDialog> </LwpoCodeManagementDialog>
+31 -11
View File
@@ -14,8 +14,10 @@ type UpCodeSectionProps = {
function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) { function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [upCodeQuery, setUpCodeQuery] = useState<UpCodeParams>({ const [keyword, setKeyword] = useState("");
const [searchParams, setSearchParams] = useState<UpCodeParams>({
keyword: "", keyword: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -23,10 +25,10 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.UP_CODE, pagination], queryKey: [ENDPOINT.CMS_SERVICE.UP_CODE, searchParams, pagination],
queryFn: () => queryFn: () =>
UpCodeApi.getUpCodeList({ UpCodeApi.getUpCodeList({
...upCodeQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -40,7 +42,22 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keyword,
});
};
const handleReset = () => {
setKeyword("");
setSearchParams({
...searchParams,
keyword: "",
});
}; };
const { tableComponent, tableInstance: table } = useDataTable({ const { tableComponent, tableInstance: table } = useDataTable({
@@ -66,17 +83,20 @@ function UpCodeSection({ handleSelectedUpCd }: UpCodeSectionProps) {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={upCodeQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setUpCodeQuery({
...upCodeQuery,
keyword: e.target.value,
})
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -60,7 +60,7 @@ function ContentsForm({
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>콘텐츠 제목</TableHead> <TableHead data-required>콘텐츠 제목</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -82,7 +82,7 @@ function ContentsForm({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>콘텐츠 내용</TableHead> <TableHead data-required>콘텐츠 내용</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -228,7 +228,7 @@ function ContentsForm({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용여부</TableHead> <TableHead data-required>사용여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -17,19 +17,21 @@ import { columns } from "../model/table-columns";
function ContentsPage() { function ContentsPage() {
const router = useRouter(); const router = useRouter();
const [contentsQuery, setContentsQuery] = useState<ContentsParams>({ const [contsTtl, setContsTtl] = useState("");
const [searchParams, setSearchParams] = useState<ContentsParams>({
contsTtl: "", contsTtl: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 5, pageSize: 10,
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS, pagination], queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS, searchParams, pagination],
queryFn: () => queryFn: () =>
ContentsApi.getContentsList({ ContentsApi.getContentsList({
...contentsQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -38,7 +40,22 @@ function ContentsPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
contsTtl,
});
};
const handleReset = () => {
setContsTtl("");
setSearchParams({
...searchParams,
contsTtl: "",
});
}; };
const handleRowClick = (row: Contents) => { const handleRowClick = (row: Contents) => {
@@ -72,12 +89,20 @@ function ContentsPage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={contentsQuery.contsTtl} value={contsTtl}
onChange={(e) => setContentsQuery({ contsTtl: e.target.value })} onChange={(e) => setContsTtl(e.target.value)}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+24 -6
View File
@@ -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>({ const form = useForm<ContentsFormData>({
resolver: zodResolver(contentsFormSchema), resolver: zodResolver(contentsFormSchema),
values: contentsDetail, values: contentsDetail,
@@ -107,14 +117,20 @@ function ModifyPage({ sequential }: { sequential: string }) {
(contentsVersionList?.content?.[0]?.contsVer ?? 0) > 1; (contentsVersionList?.content?.[0]?.contsVer ?? 0) > 1;
const onRequestApprove = async () => { const onRequestApprove = async () => {
const message = if (
"승인 요청하면 더이상 내용을 수정할 수 없습니다.\n요청하시겠습니까?"; await confirm(
if (await confirm(message)) requestApprove(); "승인 요청하면 더이상 내용을 수정할 수 없습니다.\n요청하시겠습니까?",
)
)
requestApprove();
}; };
const onApprove = async () => { const onApprove = async () => {
const message = "승인하시겠습니까?"; if (await confirm("승인하시겠습니까?")) approveContents();
if (await confirm(message)) approveContents(); };
const onReject = async () => {
if (await confirm("반려하시겠습니까?")) rejectContents();
}; };
useEffect(() => { useEffect(() => {
@@ -158,7 +174,9 @@ function ModifyPage({ sequential }: { sequential: string }) {
{approveStatus === CONTENTS_APPROVE_STATUS.REQUESTED && ( {approveStatus === CONTENTS_APPROVE_STATUS.REQUESTED && (
<> <>
<Button onClick={onApprove}>승인</Button> <Button onClick={onApprove}>승인</Button>
<Button color="gray">반려</Button> <Button color="gray" onClick={onReject}>
반려
</Button>
</> </>
)} )}
</div> </div>
+31 -13
View File
@@ -14,8 +14,10 @@ function FilePage() {
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage); const alert = useAlertStore((state) => state.setMessage);
const [attachmentQuery, setAttachmentQuery] = useState<AttachmentParams>({ const [orgnlFileNm, setOrgnlFileNm] = useState("");
const [searchParams, setSearchParams] = useState<AttachmentParams>({
orgnlFileNm: "", orgnlFileNm: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -23,10 +25,10 @@ function FilePage() {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, pagination], queryKey: [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS, searchParams, pagination],
queryFn: () => queryFn: () =>
AttachmentApi.getAttachmentsList({ AttachmentApi.getAttachmentsList({
...attachmentQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, 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>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
orgnlFileNm,
});
};
const handleReset = () => {
setOrgnlFileNm("");
setSearchParams({
...searchParams,
orgnlFileNm: "",
});
}; };
return ( return (
@@ -86,10 +98,8 @@ function FilePage() {
<Input <Input
id="keyword" id="keyword"
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={attachmentQuery.orgnlFileNm || ""} value={orgnlFileNm}
onChange={(e) => onChange={(e) => setOrgnlFileNm(e.target.value)}
handleAttachmentQueryChange("orgnlFileNm")(e.target.value)
}
placeholder="파일명을 입력하세요" placeholder="파일명을 입력하세요"
/> />
</div> </div>
@@ -97,6 +107,14 @@ function FilePage() {
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -4,7 +4,11 @@ import { type GroupListResponseDto } from "@/entities/group";
import { MenuAuthorityDialog } from "../ui/menu-authority-dialog"; import { MenuAuthorityDialog } from "../ui/menu-authority-dialog";
import { GroupsDialog } from "../ui/groups-dialog"; import { GroupsDialog } from "../ui/groups-dialog";
export const columns: ColumnDef<GroupListResponseDto>[] = [ export const createColumns = ({
onDelete,
}: {
onDelete: (groupId: string) => void;
}): ColumnDef<GroupListResponseDto>[] => [
{ {
accessorKey: "groupId", accessorKey: "groupId",
header: "그룹코드", header: "그룹코드",
@@ -20,18 +24,28 @@ export const columns: ColumnDef<GroupListResponseDto>[] = [
{ {
id: "actions", id: "actions",
header: "관리", header: "관리",
cell: ({ row }) => ( cell: ({ row }) => {
<div const handleDelete = (e: React.MouseEvent) => {
onClick={(e) => e.stopPropagation()} e.stopPropagation();
className="flex justify-center gap-2" onDelete(row.original.groupId);
> };
<MenuAuthorityDialog groupId={row.original.groupId} />
<GroupsDialog groupId={row.original.groupId}> return (
<Button size="sm" color="white"> <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> </Button>
</GroupsDialog> </div>
</div> );
), },
}, },
]; ];
+5 -5
View File
@@ -37,7 +37,7 @@ function GroupForm({ form }: GroupFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>그룹코드</TableHead> <TableHead data-required>그룹코드</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -59,7 +59,7 @@ function GroupForm({ form }: GroupFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>그룹명</TableHead> <TableHead data-required>그룹명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -81,7 +81,7 @@ function GroupForm({ form }: GroupFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>그룹구분</TableHead> <TableHead data-required>그룹구분</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -146,7 +146,7 @@ function GroupForm({ form }: GroupFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>관리권한</TableHead> <TableHead data-required>관리권한</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -176,7 +176,7 @@ function GroupForm({ form }: GroupFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>권한</TableHead> <TableHead data-required>권한</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -12,7 +12,7 @@ import {
type RowSelectionState, type RowSelectionState,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; 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 { GroupUsersApi } from "@/entities/group-user";
import { ENDPOINT } from "@/shared/config"; import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useDataTable } from "@/shared/lib"; import { useAlertStore, useDataTable } from "@/shared/lib";
@@ -32,13 +32,17 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [userNm, setUserNm] = useState("");
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination], queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination, userNm],
queryFn: () => queryFn: () =>
GroupUsersApi.findPageGroupUser({ GroupUsersApi.findPageGroupUser({
keyword: groupId, keyword: groupId,
userNm: userNm,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -46,6 +50,20 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
placeholderData: keepPreviousData, 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({ const { tableComponent, tableInstance: table } = useDataTable({
query, query,
tableOptions: { tableOptions: {
@@ -97,6 +115,29 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) {
return ( return (
<div className="flex-1"> <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 card-border">
<div className="card-header"> <div className="card-header">
<h3 className="text-2xl font-medium text-label">그룹 사용자 목록</h3> <h3 className="text-2xl font-medium text-label">그룹 사용자 목록</h3>
@@ -43,7 +43,7 @@ const GroupsDialog = memo(
const { data: group } = useQuery({ const { data: group } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, groupId], queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, groupId],
queryFn: () => GroupApi.findGroupById(groupId!), queryFn: () => GroupApi.findGroupById(groupId!),
enabled: !!groupId, enabled: !!groupId && open, // open 시점 groupId가 있을 때만 상세 조회 API 호출
}); });
const { mutateAsync: createGroup } = useMutationWrapper({ const { mutateAsync: createGroup } = useMutationWrapper({
+98 -7
View File
@@ -1,14 +1,20 @@
"use client"; "use client";
import { useState } from "react"; 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 { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
import { Button } from "@common/components"; import { Button, Input, Label } from "@common/components";
import { GroupApi } from "@/entities/group"; import { GroupApi , type GroupSearchParams} from "@/entities/group";
import { ENDPOINT } from "@/shared/config"; import { ENDPOINT } from "@/shared/config";
import { useDataTable } from "@/shared/lib"; 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 { GroupsDialog } from "./groups-dialog";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
type GroupsSectionProps = { type GroupsSectionProps = {
handleSelectedGroup: (groupId: string) => void; handleSelectedGroup: (groupId: string) => void;
@@ -20,25 +26,73 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) {
pageSize: 10, pageSize: 10,
}); });
const [groupNm, setGroupNm] = useState("");
const [searchParams, setSearchParams] = useState<GroupSearchParams>({
groupNm: "",
delYn :"N",
sort : "groupId,desc",
});
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, pagination], queryKey: [ENDPOINT.CMS_SERVICE.GROUPS,searchParams, pagination],
queryFn: () => queryFn: () =>
GroupApi.findPageGroup({ GroupApi.findPageGroup({
...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
placeholderData: keepPreviousData, 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 }) => { const handleRowClick = (row: { groupId: string }) => {
table.setRowSelection({ [row.groupId]: true }); table.setRowSelection({ [row.groupId]: true });
handleSelectedGroup(row.groupId); handleSelectedGroup(row.groupId); // 1. 리스트에서 선택된 groupId 저장
}; };
const { tableComponent, tableInstance: table } = useDataTable({ const { tableComponent, tableInstance: table } = useDataTable({
query, query,
tableOptions: { tableOptions: {
columns, columns: createColumns({ onDelete: handleDelete }),
state: { pagination }, state: { pagination },
manualPagination: true, manualPagination: true,
enableMultiRowSelection: false, enableMultiRowSelection: false,
@@ -54,6 +108,43 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) {
return ( return (
<div className="flex-1"> <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 card-border">
<div className="card-header"> <div className="card-header">
<h3 className="text-2xl font-medium text-label">그룹 목록</h3> <h3 className="text-2xl font-medium text-label">그룹 목록</h3>
+26 -23
View File
@@ -42,8 +42,9 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
file: uploadFile, file: uploadFile,
response: uploadResponse, response: uploadResponse,
progress, progress,
isValid,
setFile, setFile,
} = useTus(); } = useTus({ accept: ".jpeg,.jpg,.png" });
const setAttachmentFileCode = useAttachmentStore( const setAttachmentFileCode = useAttachmentStore(
(state) => state.setAttachmentFileCode, (state) => state.setAttachmentFileCode,
); );
@@ -90,7 +91,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>이미지명</TableHead> <TableHead data-required>이미지명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -111,7 +112,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>정렬순서</TableHead> <TableHead data-required>정렬순서</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -137,7 +138,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>이미지유형</TableHead> <TableHead data-required>이미지유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -157,7 +158,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>첨부파일</TableHead> <TableHead data-required>첨부파일</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -208,24 +209,26 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
customPreview={ customPreview={
<> <>
<Label htmlFor="atflCd"> <Label htmlFor="atflCd">
<div className="relative h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent"> <div className="h-[27rem] w-[48rem] overflow-hidden rounded-sm border border-border bg-accent">
<div className="relative h-full w-full"> {isValid && (
{imageUrl && ( <div className="relative size-full">
<Image {imageUrl && (
src={imageUrl} <Image
className="object-contain" src={imageUrl}
fill className="object-contain"
alt="업로드된 이미지" 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>
</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> </Label>
<div className="mb-2 text-lg font-medium text-label"> <div className="mb-2 text-lg font-medium text-label">
@@ -269,7 +272,7 @@ function ImageForm({ form, imgSn, ...props }: ImageFormProps) {
</TableRow> </TableRow>
)} )}
<TableRow> <TableRow>
<TableHead>사용여부</TableHead> <TableHead data-required>사용여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+35 -13
View File
@@ -24,22 +24,23 @@ type PostingTarget = {
function ImagePage() { function ImagePage() {
const router = useRouter(); const router = useRouter();
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [keyword, setKeyword] = useState("");
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]); const [searchParams, setSearchParams] = useState<ImageParams>({
const [imageQuery, setImageQuery] = useState<ImageParams>({ keyword: "",
keyword: "" , sort: "regDt,desc",
sort:"regDt,desc"
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 5, pageSize: 10,
}); });
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.IMAGES, pagination], queryKey: [ENDPOINT.CMS_SERVICE.IMAGES, searchParams, pagination],
queryFn: () => queryFn: () =>
ImageApi.getImageList({ ImageApi.getImageList({
...imageQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -47,7 +48,22 @@ function ImagePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keyword,
});
};
const handleReset = () => {
setKeyword("");
setSearchParams({
...searchParams,
keyword: "",
});
}; };
const handleRowClick = (row: Image) => { const handleRowClick = (row: Image) => {
@@ -99,14 +115,20 @@ function ImagePage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={imageQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setImageQuery({ ...imageQuery, keyword: e.target.value })
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+45 -33
View File
@@ -27,8 +27,7 @@ type DateRange = {
to?: Date | undefined; to?: Date | undefined;
}; };
function LoginlogPage({}) { function LoginlogPage() {
// 1주일 전부터 오늘까지 기본 날짜 설정
const defaultDateRange: DateRange = { const defaultDateRange: DateRange = {
from: dayjs().subtract(7, "day").toDate(), from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(), to: dayjs().toDate(),
@@ -41,24 +40,21 @@ function LoginlogPage({}) {
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [userId, setUserId] = useState("");
const [loginlogPostQuery, setLoginlogPostQuery] = useState<LoginlogParams>({ const [errOcrnYn, setErrOcrnYn] = useState("N");
const [searchParams, setSearchParams] = useState<LoginlogParams>({
userId: "", userId: "",
siteId: "",
errOcrnYn: "N", errOcrnYn: "N",
remoteIp: "",
lgnMthd: "",
lgnErrCd: "",
searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
searchDateTo: dayjs().format("YYYY-MM-DD"), searchDateTo: dayjs().format("YYYY-MM-DD"),
sort: "lgnDt,desc", sort: "lgnDt,desc",
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination], queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, searchParams, pagination],
queryFn: () => queryFn: () =>
LoginlogApi.getLoginlogList({ LoginlogApi.getLoginlogList({
...loginlogPostQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -85,15 +81,34 @@ function LoginlogPage({}) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
setIsManualLoading(true); 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(() => { query.refetch().finally(() => {
setIsManualLoading(false); setIsManualLoading(false);
}); });
}; };
const handleLoginlogParamsChange = const handleReset = () => {
(key: keyof LoginlogParams) => (value: unknown) => { setUserId("");
setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); 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) => { const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange | undefined; let presetDateRange: DateRange | undefined;
@@ -119,19 +134,7 @@ function LoginlogPage({}) {
default: default:
presetDateRange = undefined; presetDateRange = undefined;
} }
handleDateRangeChange(presetDateRange); setDateRange(presetDateRange || defaultDateRange);
};
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"),
});
}; };
return ( return (
@@ -141,7 +144,7 @@ function LoginlogPage({}) {
<Label className="mr-2">조회기간</Label> <Label className="mr-2">조회기간</Label>
<DateRangePicker <DateRangePicker
value={dateRange} value={dateRange}
onValueChange={handleDateRangeChange} onValueChange={(value) => setDateRange(value || defaultDateRange)}
> >
<div className="card card-border flex items-center gap-2"> <div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}> <Select onValueChange={handlePresetDateRange}>
@@ -159,13 +162,14 @@ function LoginlogPage({}) {
<Label className="ml-4 mr-2 text-label">사용자ID</Label> <Label className="ml-4 mr-2 text-label">사용자ID</Label>
<Input <Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={loginlogPostQuery.userId} value={userId}
onChange={(e) => onChange={(e) => setUserId(e.target.value)}
handleLoginlogParamsChange("userId")(e.target.value)
}
/> />
<Label className="ml-4 mr-2 text-label">상태</Label> <Label className="ml-4 mr-2 text-label">상태</Label>
<Select onValueChange={handleLoginlogParamsChange("errOcrnYn")}> <Select
value={errOcrnYn}
onValueChange={(value) => setErrOcrnYn(value)}
>
<SelectTrigger className="w-[10rem]"> <SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
</SelectTrigger> </SelectTrigger>
@@ -183,6 +187,14 @@ function LoginlogPage({}) {
? "조회 중..." ? "조회 중..."
: "조회"} : "조회"}
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <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();
+2 -1
View File
@@ -3,7 +3,7 @@ import type {
TreeUserOrganization, TreeUserOrganization,
TreeUser, TreeUser,
} from "@/entities/user-organization"; } from "@/entities/user-organization";
import type { TreeMenu, TreeNode } from "../model/menu-interface"; import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
export const generateMenuTreeData = ( export const generateMenuTreeData = (
data: TreeMenu[], data: TreeMenu[],
@@ -153,6 +153,7 @@ export const filterUserOrganizationTreeDataByKeyword = (
): NodeModel<TreeUserOrganization | TreeUser>[] => { ): NodeModel<TreeUserOrganization | TreeUser>[] => {
return Array.from( return Array.from(
treeData.reduce((matchedNodes, node) => { treeData.reduce((matchedNodes, node) => {
if (node.data && "ognzSn" in node.data) return matchedNodes; // 조직 노드는 필터링 제외
if (!node.text.includes(keyword)) return matchedNodes; if (!node.text.includes(keyword)) return matchedNodes;
matchedNodes.add(node); 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>;
}
+2 -2
View File
@@ -10,8 +10,8 @@ import {
TableHead, TableHead,
TableRow, TableRow,
} from "@common/components"; } from "@common/components";
import { MENU_TYPE, type MenuFormData } from "@/entities/menu";
import { FormItem, FormControl, FormField } from "@/shared/ui/form"; import { FormItem, FormControl, FormField } from "@/shared/ui/form";
import { MENU_TYPE, type MenuFormData } from "../model/menu-interface";
import { InfoTooltip } from "@/shared/ui/info-tooltip"; import { InfoTooltip } from "@/shared/ui/info-tooltip";
interface BaseMenuFormProps { interface BaseMenuFormProps {
@@ -85,7 +85,7 @@ function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>메뉴 유형</TableHead> <TableHead data-required>메뉴 유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+120 -28
View File
@@ -1,11 +1,23 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import type { UseFormReturn } from "react-hook-form"; import type { UseFormReturn } from "react-hook-form";
import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components"; import {
import { Form } from "@/shared/ui/form"; Button,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
LNB,
} from "@common/components";
import { MenuApi, menuFormSchema, type MenuFormData } from "@/entities/menu";
import { ManagementAuthorityDialog } from "@/entities/menu-authority"; 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 { BaseMenuForm } from "./base-menu-form";
import { TypeMenuForm } from "./type-menu-form"; import { TypeMenuForm } from "./type-menu-form";
import { MenuHistoryDialog } from "./menu-history-dialog";
interface MenuFormProps { interface MenuFormProps {
form: UseFormReturn<MenuFormData>; form: UseFormReturn<MenuFormData>;
@@ -29,8 +41,22 @@ function MenuForm({
handleDelete, handleDelete,
}: MenuFormProps) { }: MenuFormProps) {
const isModifyMode = !!menuCd; const isModifyMode = !!menuCd;
const [open, setOpen] = useState(false);
const { confirm } = useConfirm(); 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) => { const onSubmit = async (data: MenuFormData) => {
if (isModifyMode) { 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 menuType = form.watch("menuLnkgTypeCd");
const isSubmitDisabled = !form.watch("siteId"); const isSubmitDisabled = !form.watch("siteId");
return ( return (
<Form {...form}> <>
<form onSubmit={form.handleSubmit(onSubmit)}> <Form {...form}>
<div className="card card-border"> <form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card-header"> <div className="card card-border">
<h3 className="text-2xl font-medium text-label"> <div className="card-header">
{isModifyMode ? "메뉴 수정" : "메뉴 등록"} <h3 className="text-2xl font-medium text-label">
</h3> {isModifyMode ? "메뉴 수정" : "메뉴 등록"}
<ManagementAuthorityDialog menuCd={menuCd} /> </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>
<div className="card"> <div className="flex justify-center gap-4 pb-12 pt-10">
<BaseMenuForm form={form} menuCd={menuCd} /> {isModifyMode && (
<TypeMenuForm form={form} menuType={menuType} /> <Button
</div> size="lg"
</div> color="white"
<div className="flex justify-center gap-4 pb-12 pt-10"> type="button"
<Button size="lg" type="submit" disabled={isSubmitDisabled}> onClick={handleLoadTemporaryData}
저장 >
</Button> 불러오기
{isModifyMode && ( </Button>
<Button size="lg" color="red" type="button" onClick={onDelete}> )}
삭제 {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> </Button>
)} {isModifyMode && (
</div> <Button size="lg" color="red" type="button" onClick={onDelete}>
</form> 삭제
</Form> </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 };
+3 -3
View File
@@ -4,8 +4,8 @@ import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview";
import { Button, cn } from "@common/components"; import { Button, cn } from "@common/components";
import { useMutationWrapper } from "@/shared/lib"; import { useMutationWrapper } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config"; import { ENDPOINT } from "@/shared/config";
import { MenuApi } from "../api/menu-service"; import { TreeMenuApi } from "../api/tree-menu-service";
import type { TreeMenu } from "../model/menu-interface"; import type { TreeMenu } from "../model/tree-menu-interface";
import { EditableNode } from "./editable-node"; import { EditableNode } from "./editable-node";
type MenuNodeProps = { type MenuNodeProps = {
@@ -37,7 +37,7 @@ function MenuNode({
const { mutateAsync: updateMenuName } = useMutationWrapper({ const { mutateAsync: updateMenuName } = useMutationWrapper({
mutationFn: (nodeName: string) => { mutationFn: (nodeName: string) => {
const encodedName = encodeURIComponent(nodeName); const encodedName = encodeURIComponent(nodeName);
return MenuApi.updateMenuName(id as number, encodedName); return TreeMenuApi.updateMenuName(id as number, encodedName);
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
+9 -8
View File
@@ -14,19 +14,20 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@common/components"; } 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 { import {
MenuApi,
MENU_TYPE, MENU_TYPE,
menuFormSchema, menuFormSchema,
type MenuFormData, 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 { MenuTree } from "./menu-tree";
import { MenuForm } from "./menu-form"; import { MenuForm } from "./menu-form";
import { generateMenuTreeData } from "../lib/utils";
function MenuPage() { function MenuPage() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -46,7 +47,7 @@ function MenuPage() {
const { data: menuTreeData } = useQuery({ const { data: menuTreeData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId],
queryFn: () => MenuApi.getTreeMenuList(siteId), queryFn: () => TreeMenuApi.getTreeMenuList(siteId),
select: (data) => { select: (data) => {
const treeData = generateMenuTreeData(data); const treeData = generateMenuTreeData(data);
const defaultSortSeq = const defaultSortSeq =
+4 -3
View File
@@ -11,8 +11,9 @@ import {
import { Button } from "@common/components"; import { Button } from "@common/components";
import { ENDPOINT } from "@/shared/config"; import { ENDPOINT } from "@/shared/config";
import { useMutationWrapper } from "@/shared/lib"; import { useMutationWrapper } from "@/shared/lib";
import { MenuApi } from "../api/menu-service"; import type { MenuFormData } from "@/entities/menu";
import type { MenuFormData, TreeMenu, TreeNode } from "../model/menu-interface"; import { TreeMenuApi } from "../api/tree-menu-service";
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
import { handleDropNode, handleSelectNode } from "../lib/utils"; import { handleDropNode, handleSelectNode } from "../lib/utils";
import { MenuNode } from "./menu-node"; import { MenuNode } from "./menu-node";
import { EditableNode } from "./editable-node"; import { EditableNode } from "./editable-node";
@@ -41,7 +42,7 @@ function MenuTree({
const { mutate: updateTreeMenu } = useMutationWrapper({ const { mutate: updateTreeMenu } = useMutationWrapper({
mutationFn: (newTree: TreeNode) => mutationFn: (newTree: TreeNode) =>
MenuApi.updateTreeMenu(siteId, [newTree]), TreeMenuApi.updateTreeMenu(siteId, [newTree]),
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS], queryKey: [ENDPOINT.CMS_SERVICE.MENUS],
@@ -12,7 +12,9 @@ import {
import { import {
Button, Button,
Dialog, Dialog,
DialogClose,
DialogContent, DialogContent,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
@@ -52,10 +54,22 @@ function OrganizationSearchDialog({
}); });
const handleSelectNode = (node: NodeModel<TreeModel>) => { const handleSelectNode = (node: NodeModel<TreeModel>) => {
if (node.data && "ognzSn" in node.data) return;
setSelectedNode(node); setSelectedNode(node);
handleUserSelect(node.data as TreeUser); 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(() => { useEffect(() => {
setTree(treeData ?? []); setTree(treeData ?? []);
}, [treeData]); }, [treeData]);
@@ -73,21 +87,25 @@ function OrganizationSearchDialog({
<DialogTitle>담당자 조회</DialogTitle> <DialogTitle>담당자 조회</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="card card-border !m-0 flex w-full items-center gap-2"> <div className="card card-border !m-0 w-full">
<Input <form onSubmit={handleSubmit} className="flex items-center gap-2">
className="h-[2.5rem] !text-[1.3rem]" <Input
value={keyword} className="h-[2.5rem] !text-[1.3rem]"
onChange={(e) => setKeyword(e.target.value)} value={keyword}
/> onChange={(e) => setKeyword(e.target.value)}
<Button />
size="sm" <Button type="submit" size="sm">
onClick={() => 조회
setTree(filterUserOrganizationTreeDataByKeyword(tree, keyword)) </Button>
} <Button
> type="button"
<i className="diveicon di-search text-base" /> size="sm"
조회 color="white"
</Button> onClick={handleReset}
>
초기화
</Button>
</form>
</div> </div>
<div className="h-[36rem] overflow-auto"> <div className="h-[36rem] overflow-auto">
<DndProvider backend={MultiBackend} options={getBackendOptions()}> <DndProvider backend={MultiBackend} options={getBackendOptions()}>
@@ -116,6 +134,16 @@ function OrganizationSearchDialog({
</DndProvider> </DndProvider>
</div> </div>
</div> </div>
<DialogFooter>
<DialogClose asChild>
<Button color="white" onClick={() => setSelectedNode(null)}>
취소
</Button>
</DialogClose>
<DialogClose asChild>
<Button disabled={!selectedNode}>확인</Button>
</DialogClose>
</DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
+4 -4
View File
@@ -16,8 +16,8 @@ import {
import type { BoardPopup } from "@/entities/board"; import type { BoardPopup } from "@/entities/board";
import type { Contents } from "@/entities/content"; import type { Contents } from "@/entities/content";
import type { TreeUser } from "@/entities/user-organization"; import type { TreeUser } from "@/entities/user-organization";
import type { MenuFormData, MenuType } from "@/entities/menu";
import { FormItem, FormControl, FormField } from "@/shared/ui/form"; import { FormItem, FormControl, FormField } from "@/shared/ui/form";
import type { MenuFormData, MenuType } from "../model/menu-interface";
import { OrganizationSearchDialog } from "./organization-search-dialog"; import { OrganizationSearchDialog } from "./organization-search-dialog";
import { ContentSearchDialog } from "./content-search-dialog"; import { ContentSearchDialog } from "./content-search-dialog";
import { BoardSearchDialog } from "./board-search-dialog"; import { BoardSearchDialog } from "./board-search-dialog";
@@ -171,7 +171,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
)} )}
{(menuType === "CONTENTS" || menuType === "BOARD") && ( {(menuType === "CONTENTS" || menuType === "BOARD") && (
<TableRow> <TableRow>
<TableHead>메뉴 평가 사용 여부</TableHead> <TableHead data-required>메뉴 평가 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -237,7 +237,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>출력 여부</TableHead> <TableHead data-required>출력 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -268,7 +268,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용 여부</TableHead> <TableHead data-required>사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+5 -5
View File
@@ -38,7 +38,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>API명</TableHead> <TableHead data-required>API명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -59,7 +59,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>API URL</TableHead> <TableHead data-required>API URL</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -80,7 +80,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>요청 메소드</TableHead> <TableHead data-required>요청 메소드</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -100,7 +100,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>정렬순서</TableHead> <TableHead data-required>정렬순서</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -126,7 +126,7 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용여부</TableHead> <TableHead data-required>사용여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+40 -20
View File
@@ -18,21 +18,23 @@ import { columns } from "../model/authorizations-table-columns";
function ModulePage() { function ModulePage() {
const router = useRouter(); const router = useRouter();
const [authorizationQuery, setAuthorizationQuery] = const [authorizationName, setAuthorizationName] = useState("");
useState<AuthorizationsParams>({ const [httpMethodCode, setHttpMethodCode] = useState("");
authorizationName: "", const [searchParams, setSearchParams] = useState<AuthorizationsParams>({
httpMethodCode: "", authorizationName: "",
}); httpMethodCode: "",
sort: "regDt,desc",
});
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS, pagination], queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS, searchParams, pagination],
queryFn: () => queryFn: () =>
AuthorizationsApi.getAuthorizationsList({ AuthorizationsApi.getAuthorizationsList({
...authorizationQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, 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>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
authorizationName,
httpMethodCode,
});
};
const handleReset = () => {
setAuthorizationName("");
setHttpMethodCode("");
setSearchParams({
...searchParams,
authorizationName: "",
httpMethodCode: "",
});
}; };
return ( return (
@@ -81,7 +96,8 @@ function ModulePage() {
</Label> </Label>
<CodeSelect <CodeSelect
upCd="HTTPMHD" upCd="HTTPMHD"
onValueChange={handleAuthorizationQueryChange("httpMethodCode")} value={httpMethodCode}
onValueChange={(value) => setHttpMethodCode(value)}
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -91,18 +107,22 @@ function ModulePage() {
<Input <Input
id="keyword" id="keyword"
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={authorizationQuery.authorizationName} value={authorizationName}
onChange={(e) => onChange={(e) => setAuthorizationName(e.target.value)}
handleAuthorizationQueryChange("authorizationName")(
e.target.value,
)
}
/> />
</div> </div>
</div> </div>
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -87,7 +87,7 @@ function OrganizationForm({
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>조직명</TableHead> <TableHead data-required>조직명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -106,7 +106,7 @@ function OrganizationForm({
)} )}
/> />
</TableCell> </TableCell>
<TableHead>정렬순서</TableHead> <TableHead data-required>정렬순서</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -41,6 +41,10 @@ export class PopupApiService implements PopupService {
validatedData, validatedData,
); );
} }
public async deletePopup(popupSn: number): Promise<void> {
await client.delete(`${ENDPOINT.CMS_SERVICE.POPUPS}/${popupSn}`);
}
} }
export const PopupApi = new PopupApiService(); export const PopupApi = new PopupApiService();
@@ -68,4 +68,5 @@ export interface PopupService {
getPopupInfo(popupSn: number): Promise<PopupInfo>; getPopupInfo(popupSn: number): Promise<PopupInfo>;
createPopup(data: PopupFormData): Promise<void>; createPopup(data: PopupFormData): Promise<void>;
updatePopup(popupSn: number, data: PopupUpdateFormData): Promise<void>; updatePopup(popupSn: number, data: PopupUpdateFormData): Promise<void>;
deletePopup(popupSn: number): Promise<void>;
} }
+13 -1
View File
@@ -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({ const { mutateAsync: savePostingHistory } = useMutationWrapper({
mutationFn: (data: PostPayload[]) => PostingApi.savePosting(data), mutationFn: (data: PostPayload[]) => PostingApi.savePosting(data),
onSuccess: () => { onSuccess: () => {
@@ -101,7 +108,12 @@ function ModifyPage({ popupSn }: { popupSn: number }) {
<div className="card-header"> <div className="card-header">
<h3 className="text-2xl font-medium text-label">팝업 수정</h3> <h3 className="text-2xl font-medium text-label">팝업 수정</h3>
</div> </div>
<PopupForm form={form} handleSave={updatePopup} /> <PopupForm
form={form}
popupSn={popupSn}
handleSave={updatePopup}
handleDelete={deletePopup}
/>
</div> </div>
<div className="card card-border"> <div className="card card-border">
<div className="card-header"> <div className="card-header">
+8 -8
View File
@@ -32,7 +32,7 @@ const Editor = dynamic(
type PopupFormProps<T extends PopupFormData | PopupUpdateFormData> = { type PopupFormProps<T extends PopupFormData | PopupUpdateFormData> = {
form: UseFormReturn<T>; form: UseFormReturn<T>;
popupSn?: string; popupSn?: number;
handleSave: (data: T) => Promise<void>; handleSave: (data: T) => Promise<void>;
handleDelete?: () => Promise<void>; handleDelete?: () => Promise<void>;
}; };
@@ -80,7 +80,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>제목</TableHead> <TableHead data-required>제목</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -101,7 +101,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>팝업 크기</TableHead> <TableHead data-required>팝업 크기</TableHead>
<TableCell className="border"> <TableCell className="border">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FormField <FormField
@@ -149,7 +149,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>팝업 위치</TableHead> <TableHead data-required>팝업 위치</TableHead>
<TableCell className="border"> <TableCell className="border">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<FormField <FormField
@@ -197,7 +197,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>팝업 유형</TableHead> <TableHead data-required>팝업 유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -216,7 +216,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>콘텐츠 유형</TableHead> <TableHead data-required>콘텐츠 유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -297,7 +297,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>팝업 그만 보기 사용 여부</TableHead> <TableHead data-required>팝업 그만 보기 사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -327,7 +327,7 @@ function PopupForm<T extends PopupFormData | PopupUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용 여부</TableHead> <TableHead data-required>사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+31 -8
View File
@@ -25,8 +25,10 @@ type PostingTarget = {
function PopupPage() { function PopupPage() {
const router = useRouter(); const router = useRouter();
const [popupParams, setPopupParams] = useState<PopupParams>({ const [keyword, setKeyword] = useState("");
const [searchParams, setSearchParams] = useState<PopupParams>({
keyword: "", keyword: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -36,10 +38,10 @@ function PopupPage() {
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]); const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POPUPS, pagination], queryKey: [ENDPOINT.CMS_SERVICE.POPUPS, searchParams, pagination],
queryFn: () => queryFn: () =>
PopupApi.getPopupList({ PopupApi.getPopupList({
...popupParams, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -48,7 +50,22 @@ function PopupPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keyword,
});
};
const handleReset = () => {
setKeyword("");
setSearchParams({
...searchParams,
keyword: "",
});
}; };
const handleRowClick = (row: Popup) => { const handleRowClick = (row: Popup) => {
@@ -99,14 +116,20 @@ function PopupPage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={popupParams.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setPopupParams({ ...popupParams, keyword: e.target.value })
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -27,7 +27,7 @@ export const columns: ColumnDef<PrivacyLog>[] = [
}, },
{ {
accessorKey: "rqstrNm", accessorKey: "rqstrNm",
header: "조회자명", header: "요청자명",
}, },
{ {
accessorKey: "rqstrIp", accessorKey: "rqstrIp",
@@ -3,10 +3,7 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useState } from "react"; import { useState } from "react";
import { keepPreviousData, useQuery } from "@tanstack/react-query"; import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
getCoreRowModel,
type PaginationState,
} from "@tanstack/react-table";
import { import {
Button, Button,
DateRangePicker, DateRangePicker,
@@ -17,51 +14,43 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@common/components"; } from "@common/components";
import { PrivacyLogPrivacy } from "@/entities/privacylog"; import { PrivacyLogPrivacy } from "@/entities/privacylog";
import { type PrivacyLogParams} from "@/entities/privacylog/model/privacylog-interface"; import type { PrivacyLogParams } from "@/entities/privacylog/model/privacylog-interface";
import { useDataTable } from "@/shared/lib"; import { useDataTable } from "@/shared/lib";
import { ENDPOINT } from "@/shared/config"; import { ENDPOINT } from "@/shared/config";
import { columns } from "@/pages/privacylog/model/table-columns"; import { columns } from "@/pages/privacylog/model/table-columns";
type DateRange = { type DateRange = {
from: Date | undefined; from: Date | undefined;
to?: Date | undefined; to?: Date | undefined;
}; };
function PrivacyLogPage({ }) { function PrivacyLogPage() {
// 1주일 전부터 오늘까지 기본 날짜 설정
const defaultDateRange: DateRange = { const defaultDateRange: DateRange = {
from: dayjs().subtract(7, "day").toDate(), from: dayjs().subtract(7, "day").toDate(),
to: dayjs().toDate(), to: dayjs().toDate(),
}; };
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
const [privacylogPostQuery, setprivacylogPostQuery] = useState< PrivacyLogParams>({ const [inqInfoKeyword, setInqInfoKeyword] = useState("");
siteId: "", const [searchParams, setSearchParams] = useState<PrivacyLogParams>({
inqInfoKeywordType: "userName", inqInfoKeywordType: "userName",
inqInfoKeyword: "", inqInfoKeyword: "",
rqstrIp: "", inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"), inqDtEnd: dayjs().format("YYYY-MM-DD"),
inqDtEnd: dayjs().format("YYYY-MM-DD"), sort: "inqDt,desc",
sort:"inqDt,asc"
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.PRIVACYLOG, pagination], queryKey: [ENDPOINT.USER_SERVICE.PRIVACYLOG, searchParams, pagination],
queryFn: () => queryFn: () =>
PrivacyLogPrivacy.getPrivacyLogList({ PrivacyLogPrivacy.getPrivacyLogList({
...privacylogPostQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -87,12 +76,27 @@ function PrivacyLogPage({ }) {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); 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 = const handleReset = () => {
(key: keyof PrivacyLogParams) => (value: unknown) => { setInqInfoKeyword("");
setprivacylogPostQuery({ ...privacylogPostQuery, [key]: value }); setDateRange(defaultDateRange);
setSearchParams({
...searchParams,
inqInfoKeyword: "",
inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"),
inqDtEnd: dayjs().format("YYYY-MM-DD"),
});
}; };
const handlePresetDateRange = (value: string) => { const handlePresetDateRange = (value: string) => {
@@ -119,23 +123,9 @@ function PrivacyLogPage({ }) {
default: default:
presetDateRange = undefined; 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 ( return (
<div className="flex-1"> <div className="flex-1">
<div className="card card-border"> <div className="card card-border">
@@ -143,7 +133,7 @@ function PrivacyLogPage({ }) {
<Label className="mr-2">조회기간</Label> <Label className="mr-2">조회기간</Label>
<DateRangePicker <DateRangePicker
value={dateRange} value={dateRange}
onValueChange={handleDateRangeChange} onValueChange={(value) => setDateRange(value || defaultDateRange)}
> >
<div className="card card-border flex items-center gap-2"> <div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}> <Select onValueChange={handlePresetDateRange}>
@@ -158,24 +148,32 @@ function PrivacyLogPage({ }) {
</Select> </Select>
</div> </div>
</DateRangePicker> </DateRangePicker>
<Label className="ml-4 mr-2 text-label">조회자명</Label> <Label className="ml-4 mr-2 text-label">조회정보</Label>
<Input <Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={privacylogPostQuery.inqInfoKeyword} value={inqInfoKeyword}
onChange={(e) => onChange={(e) => setInqInfoKeyword(e.target.value)}
handleprivacylogParamsChange("inqInfoKeyword")(e.target.value)
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
<div className="card-header"> <div className="card-header">
<div className="flex w-full items-center justify-between"> <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"> <div className="flex gap-2">
<Select <Select
value={table.getState().pagination.pageSize.toString()} value={table.getState().pagination.pageSize.toString()}
+74 -32
View File
@@ -51,28 +51,43 @@ function RecentPage() {
const { confirm } = useConfirm(); const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage); 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 [tableSort, setTableSort] = useState("regDt,desc");
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 10, pageSize: 10,
}); });
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); 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: "", keywordType: "",
keyword: "", keyword: "",
bbsId: null, bbsId: null,
delYn: "N", delYn: "N",
frDateYmd: "", frDateYmd: "",
toDateYmd: "", toDateYmd: "",
sort: "regDt,desc",
}); });
const [isDeletePostData, setIsDeletePostData] = useState(false); const [isDeletePostData, setIsDeletePostData] = useState(false);
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, pagination, tableSort], queryKey: [
ENDPOINT.BOARD_SERVICE.POSTS,
searchParams,
pagination,
tableSort,
],
queryFn: () => queryFn: () =>
PostApi.getRecentPostList({ PostApi.getRecentPostList({
...recentPostQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
sort: tableSort, sort: tableSort,
@@ -115,14 +130,44 @@ function RecentPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
setIsDeletePostData(recentPostQuery.delYn === "Y"); setIsDeletePostData(delYn === "Y");
query.refetch(); 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 = const handleReset = () => {
(key: keyof RecentPostParams) => (value: unknown) => { setKeywordType("");
setRecentPostQuery({ ...recentPostQuery, [key]: value }); 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) => { const handlePresetDateRange = (value: string) => {
let presetDateRange: DateRange | undefined; let presetDateRange: DateRange | undefined;
@@ -148,16 +193,7 @@ function RecentPage() {
default: default:
presetDateRange = undefined; presetDateRange = undefined;
} }
handleDateRangeChange(presetDateRange); setDateRange(presetDateRange);
};
const handleDateRangeChange = (value: DateRange | undefined) => {
setDateRange(value);
setRecentPostQuery({
...recentPostQuery,
frDateYmd: dayjs(value?.from).format("YYYYMMDD") ?? "",
toDateYmd: dayjs(value?.to).format("YYYYMMDD") ?? "",
});
}; };
const onSoftDeletePost = async () => { const onSoftDeletePost = async () => {
@@ -214,10 +250,7 @@ function RecentPage() {
<div className="card card-border"> <div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2"> <form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
<Label className="mr-2">날짜</Label> <Label className="mr-2">날짜</Label>
<DateRangePicker <DateRangePicker value={dateRange} onValueChange={setDateRange}>
value={dateRange}
onValueChange={handleDateRangeChange}
>
<div className="card card-border flex items-center gap-2"> <div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}> <Select onValueChange={handlePresetDateRange}>
<SelectTrigger> <SelectTrigger>
@@ -233,7 +266,10 @@ function RecentPage() {
</DateRangePicker> </DateRangePicker>
<Label className="ml-4 mr-2 text-label">게시판</Label> <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]"> <SelectTrigger className="w-[12rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
</SelectTrigger> </SelectTrigger>
@@ -247,7 +283,7 @@ function RecentPage() {
</Select> </Select>
<Label className="ml-4 mr-2 text-label">상태</Label> <Label className="ml-4 mr-2 text-label">상태</Label>
<Select onValueChange={handleRecentPostParamsChange("delYn")}> <Select value={delYn} onValueChange={(value) => setDelYn(value)}>
<SelectTrigger className="w-[10rem]"> <SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
</SelectTrigger> </SelectTrigger>
@@ -259,8 +295,8 @@ function RecentPage() {
<Label className="ml-4 mr-2 text-label">검색</Label> <Label className="ml-4 mr-2 text-label">검색</Label>
<Select <Select
value={recentPostQuery.keywordType} value={keywordType}
onValueChange={handleRecentPostParamsChange("keywordType")} onValueChange={(value) => setKeywordType(value)}
> >
<SelectTrigger className="h-[3.2rem] w-[10rem]"> <SelectTrigger className="h-[3.2rem] w-[10rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
@@ -272,15 +308,21 @@ function RecentPage() {
</SelectContent> </SelectContent>
</Select> </Select>
<Input <Input
value={recentPostQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
handleRecentPostParamsChange("keyword")(e.target.value)
}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+5 -5
View File
@@ -53,7 +53,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>권한ID</TableHead> <TableHead data-required>권한ID</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -95,7 +95,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>권한명</TableHead> <TableHead data-required>권한명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -116,7 +116,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>권한내용</TableHead> <TableHead data-required>권한내용</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -137,7 +137,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>관리권한여부</TableHead> <TableHead data-required>관리권한여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -166,7 +166,7 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용여부</TableHead> <TableHead data-required>사용여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+31 -8
View File
@@ -13,8 +13,10 @@ import { columns } from "../model/table-columns";
function RolePage() { function RolePage() {
const router = useRouter(); const router = useRouter();
const [roleQuery, setRoleQuery] = useState<RoleParams>({ const [keyword, setKeyword] = useState("");
const [searchParams, setSearchParams] = useState<RoleParams>({
keyword: "", keyword: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -22,10 +24,10 @@ function RolePage() {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.ROLES, pagination], queryKey: [ENDPOINT.USER_SERVICE.ROLES, searchParams, pagination],
queryFn: () => queryFn: () =>
RoleApi.getRoles({ RoleApi.getRoles({
...roleQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -54,7 +56,22 @@ function RolePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keyword,
});
};
const handleReset = () => {
setKeyword("");
setSearchParams({
...searchParams,
keyword: "",
});
}; };
return ( return (
@@ -66,14 +83,20 @@ function RolePage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={roleQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setRoleQuery({ ...roleQuery, keyword: e.target.value })
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -34,6 +34,10 @@ class ScheduleApiService implements ScheduleService {
): Promise<void> { ): Promise<void> {
await client.put(`${ENDPOINT.CMS_SERVICE.SCHEDULES}/${schdlSn}`, data); 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(); export const ScheduleApi = new ScheduleApiService();
@@ -71,4 +71,5 @@ export interface ScheduleService {
getScheduleInfo(schdlSn: number): Promise<Schedule>; getScheduleInfo(schdlSn: number): Promise<Schedule>;
createSchedule(data: ScheduleFormData): Promise<void>; createSchedule(data: ScheduleFormData): Promise<void>;
updateSchedule(schdlSn: number, data: ScheduleUpdateFormData): Promise<void>; updateSchedule(schdlSn: number, data: ScheduleUpdateFormData): Promise<void>;
deleteSchedule(schdlSn: number): Promise<void>;
} }
+16 -1
View File
@@ -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>({ const form = useForm<ScheduleUpdateFormData>({
resolver: zodResolver(scheduleUpdateFormSchema), resolver: zodResolver(scheduleUpdateFormSchema),
values: { values: {
@@ -57,7 +67,12 @@ function ModifyPage({ schdlSn }: { schdlSn: string }) {
<div className="card-header"> <div className="card-header">
<h3 className="text-2xl font-medium text-label">스케줄 수정</h3> <h3 className="text-2xl font-medium text-label">스케줄 수정</h3>
</div> </div>
<ScheduleForm form={form} handleSave={handleSave} /> <ScheduleForm
form={form}
scheduleId={schdlSn}
handleSave={handleSave}
handleDelete={deleteSchedule}
/>
</div> </div>
); );
} }
@@ -66,7 +66,7 @@ function ScheduleForm<T extends ScheduleFormData | ScheduleUpdateFormData>({
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>제목</TableHead> <TableHead data-required>제목</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -87,7 +87,7 @@ function ScheduleForm<T extends ScheduleFormData | ScheduleUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>일정 기간</TableHead> <TableHead data-required>일정 기간</TableHead>
<TableCell className="border"> <TableCell className="border">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -243,7 +243,7 @@ function ScheduleForm<T extends ScheduleFormData | ScheduleUpdateFormData>({
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>일정 유형</TableHead> <TableHead data-required>일정 유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+4 -4
View File
@@ -70,7 +70,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사이트 구분</TableHead> <TableHead data-required>사이트 구분</TableHead>
<TableCell className="border"> <TableCell className="border">
<div className="flex gap-2"> <div className="flex gap-2">
<FormField <FormField
@@ -122,7 +122,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사이트명</TableHead> <TableHead data-required>사이트명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -287,7 +287,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>기본 사이트 여부</TableHead> <TableHead data-required>기본 사이트 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -316,7 +316,7 @@ function SiteForm({ form, siteId, ...props }: SiteFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용 여부</TableHead> <TableHead data-required>사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+30 -10
View File
@@ -14,10 +14,10 @@ import { columns } from "../model/table-columns";
function SitePage() { function SitePage() {
const router = useRouter(); const router = useRouter();
const [siteNm, setSiteNm] = useState("");
const [siteQuery, setSiteQuery] = useState<SiteParams>({ const [searchParams, setSearchParams] = useState<SiteParams>({
siteNm: "", siteNm: "",
sort:"regDt,desc" sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
@@ -25,10 +25,10 @@ function SitePage() {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES, pagination], queryKey: [ENDPOINT.CMS_SERVICE.SITES, searchParams, pagination],
queryFn: () => queryFn: () =>
SiteApi.getSiteList({ SiteApi.getSiteList({
...siteQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -37,7 +37,18 @@ function SitePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setSearchParams({
...searchParams,
siteNm,
});
};
const handleReset = () => {
setSiteNm("");
setSearchParams({
...searchParams,
siteNm: "",
});
}; };
const handleRowClick = (row: Site) => { const handleRowClick = (row: Site) => {
@@ -65,17 +76,26 @@ function SitePage() {
<div className="card card-border !mt-6"> <div className="card card-border !mt-6">
<div className="card card-border"> <div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2"> <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> </Label>
<Input <Input
name="siteNm"
value={siteNm}
onChange={(e) => setSiteNm(e.target.value)}
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" 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>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -45,6 +45,10 @@ export class SurveyApiService implements SurveyService {
await client.put(`${ENDPOINT.CMS_SERVICE.SURVEY}/${srvySn}`, data); 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> { public async getQuestionList(params: QuestionParams): Promise<QuestionList> {
const response = await client.get<QuestionList>( const response = await client.get<QuestionList>(
`${ENDPOINT.CMS_SERVICE.SURVEY_QUESTION}`, `${ENDPOINT.CMS_SERVICE.SURVEY_QUESTION}`,
+16 -1
View File
@@ -29,6 +29,13 @@ function DetailPage({ srvySn }: { srvySn: string }) {
queryFn: () => SurveyApi.getSurveyInfo(parseInt(srvySn)), queryFn: () => SurveyApi.getSurveyInfo(parseInt(srvySn)),
}); });
const { mutateAsync: deleteSurvey } = useMutationWrapper({
mutationFn: () => SurveyApi.deleteSurvey(parseInt(srvySn)),
onSuccess: () => {
alert("삭제되었습니다.", router.back);
},
});
const postHistoryQuery = useQuery({ const postHistoryQuery = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, srvySn], queryKey: [ENDPOINT.CMS_SERVICE.POSTINGS, srvySn],
queryFn: () => queryFn: () =>
@@ -77,6 +84,12 @@ function DetailPage({ srvySn }: { srvySn: string }) {
}, },
}); });
const handleDeleteSurvey = async () => {
if (await confirm("삭제하시겠습니까?")) {
deleteSurvey();
}
};
const handleDeletePostingHistory = async () => { const handleDeletePostingHistory = async () => {
const data = postingTable.getFilteredSelectedRowModel().rows.map((row) => ({ const data = postingTable.getFilteredSelectedRowModel().rows.map((row) => ({
...row.original, ...row.original,
@@ -101,7 +114,9 @@ function DetailPage({ srvySn }: { srvySn: string }) {
<Button onClick={() => router.push(`${pathname}/modify`)}> <Button onClick={() => router.push(`${pathname}/modify`)}>
수정 수정
</Button> </Button>
<Button color="red">삭제</Button> <Button color="red" onClick={handleDeleteSurvey}>
삭제
</Button>
</div> </div>
</div> </div>
<ul className="flex flex-col"> <ul className="flex flex-col">
@@ -41,7 +41,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>문항순번</TableHead> <TableHead data-required>문항순번</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -66,7 +66,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>문항유형</TableHead> <TableHead data-required>문항유형</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -87,7 +87,7 @@ function QuestionForm({ form, actionSlot, handleSave }: QuestionFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>문항내용</TableHead> <TableHead data-required>문항내용</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+2 -2
View File
@@ -29,7 +29,7 @@ function SurveyForm({ form, ...props }: SurveyFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>설문 제목</TableHead> <TableHead data-required>설문 제목</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -50,7 +50,7 @@ function SurveyForm({ form, ...props }: SurveyFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>설문 목적</TableHead> <TableHead data-required>설문 목적</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+32 -9
View File
@@ -25,21 +25,23 @@ type PostingTarget = {
function SurveyPage() { function SurveyPage() {
const router = useRouter(); const router = useRouter();
const [surveyParams, setSurveyParams] = useState<SurveyParams>({ const [srvyTtl, setSrvyTtl] = useState("");
const [searchParams, setSearchParams] = useState<SurveyParams>({
srvyTtl: "", srvyTtl: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 5, pageSize: 10,
}); });
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]); const [postingTargets, setPostingTargets] = useState<PostingTarget[]>([]);
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, pagination], queryKey: [ENDPOINT.CMS_SERVICE.SURVEY, searchParams, pagination],
queryFn: () => queryFn: () =>
SurveyApi.getSurveyList({ SurveyApi.getSurveyList({
...surveyParams, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -71,7 +73,22 @@ function SurveyPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
srvyTtl,
});
};
const handleReset = () => {
setSrvyTtl("");
setSearchParams({
...searchParams,
srvyTtl: "",
});
}; };
useEffect(() => { useEffect(() => {
@@ -99,14 +116,20 @@ function SurveyPage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
value={surveyParams.srvyTtl} value={srvyTtl}
onChange={(e) => onChange={(e) => setSrvyTtl(e.target.value)}
setSurveyParams({ ...surveyParams, srvyTtl: e.target.value })
}
/> />
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -39,7 +39,7 @@ function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>템플릿 구분</TableHead> <TableHead data-required>템플릿 구분</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -59,7 +59,7 @@ function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>템플릿명</TableHead> <TableHead data-required>템플릿명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -80,7 +80,7 @@ function TemplateForm({ form, tmpltSn, ...props }: TemplateFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>템플릿 내용</TableHead> <TableHead data-required>템플릿 내용</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+35 -19
View File
@@ -14,22 +14,23 @@ import type { Template, TemplateParams } from "../model/template-interface";
function TemplatePage() { function TemplatePage() {
const router = useRouter(); const router = useRouter();
const [keyword, setKeyword] = useState("");
const [templateQuery, setTemplateQuery] = useState<TemplateParams>({ const [tmpltSeCd, setTmpltSeCd] = useState("");
tmpltSeCd: "", const [searchParams, setSearchParams] = useState<TemplateParams>({
keyword: "", keyword: "",
sort:"regDt,desc" tmpltSeCd: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 5, pageSize: 10,
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.TEMPLATE, pagination], queryKey: [ENDPOINT.CMS_SERVICE.TEMPLATE, searchParams, pagination],
queryFn: () => queryFn: () =>
TemplateApi.getTemplateList({ TemplateApi.getTemplateList({
...templateQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -38,7 +39,21 @@ function TemplatePage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setSearchParams({
...searchParams,
keyword,
tmpltSeCd,
});
};
const handleReset = () => {
setKeyword("");
setTmpltSeCd("");
setSearchParams({
...searchParams,
keyword: "",
tmpltSeCd: "",
});
}; };
const handleRowClick = (row: Template) => { const handleRowClick = (row: Template) => {
@@ -73,10 +88,8 @@ function TemplatePage() {
</Label> </Label>
<CodeSelect <CodeSelect
upCd="TMPLTCLCD" upCd="TMPLTCLCD"
value={templateQuery.tmpltSeCd} value={tmpltSeCd}
onValueChange={(value) => onValueChange={(value) => setTmpltSeCd(value)}
setTemplateQuery({ ...templateQuery, tmpltSeCd: value })
}
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -85,19 +98,22 @@ function TemplatePage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={templateQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setTemplateQuery({
...templateQuery,
keyword: e.target.value,
})
}
/> />
</div> </div>
</div> </div>
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
+4 -4
View File
@@ -39,7 +39,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>약관 구분</TableHead> <TableHead data-required>약관 구분</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -59,7 +59,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>약관명</TableHead> <TableHead data-required>약관명</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -80,7 +80,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>약관 내용</TableHead> <TableHead data-required>약관 내용</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -99,7 +99,7 @@ function TermsForm({ form, trmsSn, ...props }: TermsFormProps) {
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableHead>사용 여부</TableHead> <TableHead data-required>사용 여부</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+39 -18
View File
@@ -15,21 +15,23 @@ import type { TermsParams, Terms } from "../model/terms-interface";
function TermsPage() { function TermsPage() {
const router = useRouter(); const router = useRouter();
const [termsQuery, setTermsQuery] = useState<TermsParams>({ const [keyword, setKeyword] = useState("");
trmsSeCd: "", const [trmsSeCd, setTrmsSeCd] = useState("");
const [searchParams, setSearchParams] = useState<TermsParams>({
keyword: "", keyword: "",
sort:"regDt,desc" trmsSeCd: "",
sort: "regDt,desc",
}); });
const [pagination, setPagination] = useState<PaginationState>({ const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: 5, pageSize: 10,
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.TERMS, pagination], queryKey: [ENDPOINT.CMS_SERVICE.TERMS, searchParams, pagination],
queryFn: () => queryFn: () =>
TermsApi.getTermsList({ TermsApi.getTermsList({
...termsQuery, ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -38,7 +40,25 @@ function TermsPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
query.refetch(); setPagination({
...pagination,
pageIndex: 0,
});
setSearchParams({
...searchParams,
keyword,
trmsSeCd,
});
};
const handleReset = () => {
setKeyword("");
setTrmsSeCd("");
setSearchParams({
...searchParams,
keyword: "",
trmsSeCd: "",
});
}; };
const handleRowClick = (row: Terms) => { const handleRowClick = (row: Terms) => {
@@ -73,10 +93,8 @@ function TermsPage() {
</Label> </Label>
<CodeSelect <CodeSelect
upCd="TRMSCLCD" upCd="TRMSCLCD"
value={termsQuery.trmsSeCd} value={trmsSeCd}
onValueChange={(value) => onValueChange={(value) => setTrmsSeCd(value)}
setTermsQuery({ ...termsQuery, trmsSeCd: value })
}
/> />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -85,19 +103,22 @@ function TermsPage() {
</Label> </Label>
<Input <Input
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]" className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
value={termsQuery.keyword} value={keyword}
onChange={(e) => onChange={(e) => setKeyword(e.target.value)}
setTermsQuery({
...termsQuery,
keyword: e.target.value,
})
}
/> />
</div> </div>
</div> </div>
<Button className="h-[3.2rem]" type="submit"> <Button className="h-[3.2rem]" type="submit">
조회 조회
</Button> </Button>
<Button
type="button"
color="white"
className="h-[3.2rem]"
onClick={handleReset}
>
초기화
</Button>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -50,6 +50,7 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
queryFn: () => queryFn: () =>
LwpoCodeApi.getLwpoCodeList({ LwpoCodeApi.getLwpoCodeList({
...searchQuery, ...searchQuery,
upCd: "AGENCYCD",
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
@@ -70,6 +71,9 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
{ {
accessorKey: "lwpoCd", accessorKey: "lwpoCd",
header: "기관코드", header: "기관코드",
meta: {
width: 200,
},
}, },
{ {
accessorKey: "lwpoCdNm", accessorKey: "lwpoCdNm",
@@ -120,11 +124,13 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
<DialogTrigger asChild> <DialogTrigger asChild>
<Button className="px-6">기관 검색</Button> <Button className="px-6">기관 검색</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}> <DialogContent
className="max-h-[80vh] max-w-[60rem] overflow-auto"
aria-describedby={undefined}
>
<DialogHeader> <DialogHeader>
<DialogTitle>기관 검색</DialogTitle> <DialogTitle>기관 검색</DialogTitle>
</DialogHeader> </DialogHeader>
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
@@ -150,9 +156,7 @@ function AgencySearchDialog({ onSelectAgency }: AgencySearchDialogProps) {
</div> </div>
</div> </div>
</form> </form>
{tableComponent} {tableComponent}
<DialogFooter className="!justify-center"> <DialogFooter className="!justify-center">
<Button <Button
size="lg" size="lg"
+21 -9
View File
@@ -3,6 +3,7 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { import {
Button, Button,
Table, Table,
@@ -18,10 +19,11 @@ import { useAlertStore, useMutationWrapper } from "@/shared/lib";
function UserDetail({ userUnqId }: { userUnqId: string }) { function UserDetail({ userUnqId }: { userUnqId: string }) {
const router = useRouter(); const router = useRouter();
const pathname = usePathname(); const pathname = usePathname();
const { confirm } = useConfirm();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const alert = useAlertStore((state) => state.setMessage); const alert = useAlertStore((state) => state.setMessage);
const { data } = useQuery({ const { data, refetch } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId], queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
queryFn: () => UserApi.findByUserUnqId(userUnqId), queryFn: () => UserApi.findByUserUnqId(userUnqId),
enabled: !!userUnqId, enabled: !!userUnqId,
@@ -31,9 +33,7 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
mutationFn: () => UserApi.lockUser(userUnqId), mutationFn: () => UserApi.lockUser(userUnqId),
onSuccess: () => { onSuccess: () => {
alert("계정 잠금이 활성화 되었습니다."); alert("계정 잠금이 활성화 되었습니다.");
queryClient.invalidateQueries({ refetch();
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
});
}, },
}); });
@@ -41,9 +41,7 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
mutationFn: () => UserApi.unlockUser(userUnqId), mutationFn: () => UserApi.unlockUser(userUnqId),
onSuccess: () => { onSuccess: () => {
alert("계정 잠금이 해제되었습니다."); alert("계정 잠금이 해제되었습니다.");
queryClient.invalidateQueries({ refetch();
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId],
});
}, },
}); });
@@ -51,12 +49,26 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
mutationFn: () => UserApi.applyUser(userUnqId), mutationFn: () => UserApi.applyUser(userUnqId),
onSuccess: () => { onSuccess: () => {
alert("사용자 승인이 완료되었습니다."); alert("사용자 승인이 완료되었습니다.");
refetch();
},
});
const { mutateAsync: deleteUser } = useMutationWrapper({
mutationFn: () => UserApi.deleteUser(userUnqId),
onSuccess: () => {
alert("회원이 삭제되었습니다.", router.back);
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: [ENDPOINT.USER_SERVICE.USERS, userUnqId], queryKey: [ENDPOINT.USER_SERVICE.USERS],
}); });
}, },
}); });
const handleDelete = async () => {
if (await confirm("삭제하시겠습니까?")) {
await deleteUser();
}
};
return ( return (
<div className="card card-border !mt-6"> <div className="card card-border !mt-6">
<div className="card-header"> <div className="card-header">
@@ -164,7 +176,7 @@ function UserDetail({ userUnqId }: { userUnqId: string }) {
<Button size="lg" onClick={() => router.push(`${pathname}/modify`)}> <Button size="lg" onClick={() => router.push(`${pathname}/modify`)}>
수정 수정
</Button> </Button>
<Button size="lg" color="red"> <Button size="lg" color="red" onClick={handleDelete}>
삭제 삭제
</Button> </Button>
</div> </div>
+7 -7
View File
@@ -140,7 +140,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
}, [laeSeCd, watchDtlInstCd]); }, [laeSeCd, watchDtlInstCd]);
const enoCdSelectData = useMemo( const enoCdSelectData = useMemo(
() => laeSeCd?.filter((code) => code.unit?.startsWith("G2")) ?? [], () => laeSeCd?.filter((code) => code.lwpoCd?.startsWith("G2")) ?? [],
[laeSeCd], [laeSeCd],
); );
@@ -179,7 +179,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
<TableBody> <TableBody>
{/* 아이디 */} {/* 아이디 */}
<TableRow> <TableRow>
<TableHead>아이디</TableHead> <TableHead data-required>아이디</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -223,7 +223,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
{/* 비밀번호 */} {/* 비밀번호 */}
<TableRow> <TableRow>
<TableHead>비밀번호</TableHead> <TableHead data-required>비밀번호</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -270,7 +270,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
{/* 비밀번호 힌트 */} {/* 비밀번호 힌트 */}
<TableRow> <TableRow>
<TableHead>비밀번호 힌트</TableHead> <TableHead data-required>비밀번호 힌트</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -316,7 +316,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
{/* 이름 */} {/* 이름 */}
<TableRow> <TableRow>
<TableHead>이름</TableHead> <TableHead data-required>이름</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -489,7 +489,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
{/* 기관구분 */} {/* 기관구분 */}
<TableRow> <TableRow>
<TableHead>기관구분</TableHead> <TableHead data-required>기관구분</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -571,7 +571,7 @@ function UsersForm<T extends UserSaveData | UserUpdateData>({
{/* 소속 */} {/* 소속 */}
<TableRow> <TableRow>
<TableHead>소속</TableHead> <TableHead data-required>소속</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+100 -48
View File
@@ -2,7 +2,6 @@
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
@@ -40,19 +39,25 @@ function UsersPage() {
pageIndex: 0, pageIndex: 0,
pageSize: 10, 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>({ const [searchParams, setSearchParams] = useState<UserSearchParams>({
defaultValues: { keywordType: "all",
keywordType: "all", keyword: "",
keyword: "", userStateCode: "",
userStateCode: "", aprvYn: "",
aprvYn: "", grpId: "",
grpId: "", frDateYmd: "",
frDateYmd: "", toDateYmd: "",
toDateYmd: "", sort: "regDt,desc",
sort: "regDt,desc"
},
}); });
const { data: userSttsStats } = useQuery({ const { data: userSttsStats } = useQuery({
@@ -61,16 +66,16 @@ function UsersPage() {
}); });
const query = useQuery({ const query = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS, pagination, watch("sort")], queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParams, pagination],
queryFn: () => queryFn: () =>
UserApi.findPageUser({ UserApi.findPageUser({
...getValues(), ...searchParams,
page: pagination.pageIndex, page: pagination.pageIndex,
size: pagination.pageSize, size: pagination.pageSize,
}), }),
}); });
const { data: userStateCode } = useQuery({ const { data: userStateCodes } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.CODE], queryKey: [ENDPOINT.CMS_SERVICE.CODE],
queryFn: () => CodeApi.getCode("USERSTTS"), queryFn: () => CodeApi.getCode("USERSTTS"),
}); });
@@ -98,7 +103,44 @@ function UsersPage() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); 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) => { const handlePresetDateRange = (value: string) => {
@@ -126,8 +168,6 @@ function UsersPage() {
presetDateRange = undefined; presetDateRange = undefined;
} }
setDateRange(presetDateRange); setDateRange(presetDateRange);
setValue("frDateYmd", dayjs(presetDateRange?.from).format("YYYYMMDD"));
setValue("toDateYmd", dayjs(presetDateRange?.to).format("YYYYMMDD"));
}; };
const getUserSttsStatsCnt = (code: string): number => { const getUserSttsStatsCnt = (code: string): number => {
@@ -135,6 +175,21 @@ function UsersPage() {
return userSttsStats.find((item) => item.sttsCd === code)?.cnt ?? 0; 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 ( return (
<div className="card card-border !mt-6"> <div className="card card-border !mt-6">
<div className="card card-border flex items-center justify-center gap-10 !bg-accent !py-6"> <div className="card card-border flex items-center justify-center gap-10 !bg-accent !py-6">
@@ -195,8 +250,8 @@ function UsersPage() {
회원정보 회원정보
</Label> </Label>
<Select <Select
{...register("keywordType")} value={keywordType}
onValueChange={(value) => setValue("keywordType", value)} onValueChange={(value) => setKeywordType(value)}
> >
<SelectTrigger className="w-[18rem]"> <SelectTrigger className="w-[18rem]">
<SelectValue placeholder="선택" /> <SelectValue placeholder="선택" />
@@ -210,7 +265,8 @@ function UsersPage() {
</SelectContent> </SelectContent>
</Select> </Select>
<Input <Input
{...register("keyword")} value={keyword}
onChange={(e) => setKeyword(e.target.value)}
className="!text-[1.3rem]" className="!text-[1.3rem]"
placeholder="검색어를 입력하세요" placeholder="검색어를 입력하세요"
/> />
@@ -221,14 +277,7 @@ function UsersPage() {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label className="mr-2 text-label">기간</Label> <Label className="mr-2 text-label">기간</Label>
<DateRangePicker <DateRangePicker value={dateRange} onValueChange={setDateRange}>
value={dateRange}
onValueChange={(value) => {
setDateRange(value);
setValue("frDateYmd", dayjs(value?.from).format("YYYYMMDD"));
setValue("toDateYmd", dayjs(value?.to).format("YYYYMMDD"));
}}
>
<div className="card card-border flex items-center gap-2"> <div className="card card-border flex items-center gap-2">
<Select onValueChange={handlePresetDateRange}> <Select onValueChange={handlePresetDateRange}>
<SelectTrigger> <SelectTrigger>
@@ -250,24 +299,14 @@ function UsersPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Label className="mr-2 text-label">회원상태</Label> <Label className="mr-2 text-label">회원상태</Label>
<div className="flex gap-4"> <div className="flex gap-4">
{userStateCode?.map((item) => ( {userStateCodes?.map((item) => (
<div key={item.lwpoCd} className="flex items-center gap-2"> <div key={item.lwpoCd} className="flex items-center gap-2">
<Checkbox <Checkbox
id={item.lwpoCd} id={item.lwpoCd}
value={item.lwpoCd} value={item.lwpoCd}
checked={watch("userStateCode")?.includes(item.lwpoCd)} checked={userStateCode?.includes(item.lwpoCd)}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setValue( handleUserStateCodeChange(item.lwpoCd, !!checked);
"userStateCode",
checked
? getValues("userStateCode")
? `${getValues("userStateCode")},${item.lwpoCd}`
: item.lwpoCd
: getValues("userStateCode")
?.split(",")
.filter((code) => code !== item.lwpoCd)
.join(","),
);
}} }}
/> />
<Label htmlFor={item.lwpoCd}>{item.lwpoCdNm}</Label> <Label htmlFor={item.lwpoCd}>{item.lwpoCdNm}</Label>
@@ -276,10 +315,20 @@ function UsersPage() {
</div> </div>
</div> </div>
<hr /> <hr />
<Button type="submit" className="mx-auto w-[16rem]"> <div className="flex justify-center gap-2">
<i className="diveicon di-search" /> <Button type="submit" className="w-[16rem]">
검색 <i className="diveicon di-search" />
</Button> 검색
</Button>
<Button
type="button"
color="white"
className="w-[16rem]"
onClick={handleReset}
>
초기화
</Button>
</div>
</form> </form>
</div> </div>
<div className="card card-border"> <div className="card card-border">
@@ -290,7 +339,10 @@ function UsersPage() {
<Select <Select
defaultValue="regDt,desc" defaultValue="regDt,desc"
onValueChange={(value) => { onValueChange={(value) => {
setValue("sort", value); setSearchParams({
...searchParams,
sort: value,
});
}} }}
> >
<SelectTrigger> <SelectTrigger>
+3 -2
View File
@@ -20,9 +20,7 @@ export const ENDPOINT = {
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations", ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
ROLES: "/user-service/api/v1/roles", ROLES: "/user-service/api/v1/roles",
LOGINLOG: "/user-service/api/v1/login-logs", LOGINLOG: "/user-service/api/v1/login-logs",
APILOG: "/user-service/api/v1/api-logs",
PRIVACYLOG: "/user-service//api/v1/prvc-inq-logs", PRIVACYLOG: "/user-service//api/v1/prvc-inq-logs",
}, },
CMS_SERVICE: { CMS_SERVICE: {
SITES: "/cms-service/api/v1/sites", SITES: "/cms-service/api/v1/sites",
@@ -33,6 +31,8 @@ export const ENDPOINT = {
MENUS: "/cms-service/api/v1/menus", MENUS: "/cms-service/api/v1/menus",
MENU_ROLES: "/cms-service/api/v1/menu-roles", MENU_ROLES: "/cms-service/api/v1/menu-roles",
MENU_AUTHRTS: "/cms-service/api/v1/menu-authrts", 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: "/cms-service/api/v1/contents",
CONTENTS_DETAILS: "/cms-service/api/v1/contents-details", CONTENTS_DETAILS: "/cms-service/api/v1/contents-details",
ORGANIZATIONS: "/cms-service/api/v1/ognzs", ORGANIZATIONS: "/cms-service/api/v1/ognzs",
@@ -52,6 +52,7 @@ export const ENDPOINT = {
GROUPS: "/cms-service/api/v1/groups", GROUPS: "/cms-service/api/v1/groups",
GROUP_USERS: "/cms-service/api/v1/group-users", GROUP_USERS: "/cms-service/api/v1/group-users",
MANAGER_IP: "/cms-service/api/v1/mgrip", MANAGER_IP: "/cms-service/api/v1/mgrip",
APILOG: "/cms-service/api/v1/api-logs",
}, },
BOARD_SERVICE: { BOARD_SERVICE: {
BBS: "/board-service/api/v1/bbs", BBS: "/board-service/api/v1/bbs",
@@ -17,15 +17,13 @@ export const useMutationWrapper = <
let errorMessage = ""; let errorMessage = "";
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
if (error.response?.status)
errorMessage += `status: ${error.response.status}\n`;
if (error.response?.data?.message) if (error.response?.data?.message)
errorMessage += `${error.response.data.message}\n`; errorMessage += `${error.response.data.message}\n`;
if (error.response?.data?.errors[0]?.message) if (error.response?.data?.errors[0]?.message)
errorMessage += `${error.response.data.errors[0].message}\n`; errorMessage += `${error.response.data.errors[0].message}\n`;
} }
alert(`[Error] ${errorMessage}`); alert(`${errorMessage}`);
}, },
}); });
}; };
+27 -1
View File
@@ -2,9 +2,15 @@ import { useEffect, useState } from "react";
import { type HttpResponse, Upload } from "tus-js-client"; import { type HttpResponse, Upload } from "tus-js-client";
import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "../config"; import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "../config";
import { useAttachmentStore } from "./use-attachment-store"; import { useAttachmentStore } from "./use-attachment-store";
import { useAlertStore } from "./use-alert-store";
import { getCookie } from "./cookie"; 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( const { attachmentFileCode, setAttachmentFileCode } = useAttachmentStore(
(state) => state, (state) => state,
); );
@@ -13,6 +19,7 @@ export const useTus = () => {
const [upload, setUpload] = useState<Upload | null>(null); const [upload, setUpload] = useState<Upload | null>(null);
const [response, setResponse] = useState<HttpResponse | null>(null); const [response, setResponse] = useState<HttpResponse | null>(null);
const [progress, setProgress] = useState<number>(0); const [progress, setProgress] = useState<number>(0);
const [isValid, setIsValid] = useState<boolean>(false);
const metadata = { const metadata = {
filename: file?.name ?? "", filename: file?.name ?? "",
@@ -20,6 +27,23 @@ export const useTus = () => {
atflCd: attachmentFileCode, 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 = () => { const createUpload = () => {
return new Upload(file!, { return new Upload(file!, {
endpoint: `${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`, endpoint: `${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`,
@@ -48,6 +72,7 @@ export const useTus = () => {
useEffect(() => { useEffect(() => {
if (!file) return; if (!file) return;
if (!validFileType(file)) return;
const upload = createUpload(); const upload = createUpload();
upload.start(); upload.start();
@@ -59,6 +84,7 @@ export const useTus = () => {
upload, upload,
response, response,
progress, progress,
isValid,
setFile, setFile,
}; };
}; };
@@ -102,7 +102,7 @@ function GeneralFileUploader<T extends { atflCd?: string | null }>({
<span>{attachment.orgnlFileNm}</span> <span>{attachment.orgnlFileNm}</span>
<Button <Button
color="lightGray" 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={() => onClick={() =>
setDeleteAttachmentIds([...deleteAttachmentIds, atflId]) setDeleteAttachmentIds([...deleteAttachmentIds, atflId])
} }
+2 -2
View File
@@ -77,7 +77,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
</colgroup> </colgroup>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableHead>제목</TableHead> <TableHead data-required>제목</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
@@ -120,7 +120,7 @@ function PostForm<T extends PostFormData | PostUpdateFormData>({
</TableRow> </TableRow>
{!userInfo?.userUnqId && ( {!userInfo?.userUnqId && (
<TableRow> <TableRow>
<TableHead>비밀번호</TableHead> <TableHead data-required>비밀번호</TableHead>
<TableCell className="border"> <TableCell className="border">
<FormField <FormField
control={form.control} control={form.control}
+25
View File
@@ -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
+2 -2
View File
@@ -3,8 +3,8 @@ NEXT_PUBLIC_PORT = 11302
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr: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_ADDR_URL = http://con.mind-one.co.kr:11082
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함 NEXT_PUBLIC_LX_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver_swl
NEXT_PUBLIC_RSONE_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터 NEXT_PUBLIC_RSONE_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터
NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_AUTH_USER_ID = token-id
+1 -1
View File
@@ -3,7 +3,7 @@ NEXT_PUBLIC_PORT = 11301
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr: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_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver 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_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_ACCESS_TOKEN = access-token
+2 -2
View File
@@ -3,12 +3,12 @@ NEXT_PUBLIC_PORT = 3001
NEXT_PUBLIC_PROXY_HOST = http://localhost:3001 NEXT_PUBLIC_PROXY_HOST = http://localhost:3001
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver 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_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-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://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 # 박이정 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
+12
View File
@@ -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
View File
@@ -3,23 +3,38 @@ import { BaseLayout } from "@common/components";
import "./globals.css"; import "./globals.css";
import { StoreProvider } from "./store"; import { StoreProvider } from "./store";
import { MainDiv } from "@common/business_components"; 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({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
i18next.init({
lng: 'ko',
fallbackLng: 'ko',
resources: {
ko: {
base: koBase,
},
}
});
return ( return (
<html lang="en"> <html lang="en">
<body> <body>
<I18nextProvider i18n={i18next}>
<StoreProvider> <StoreProvider>
<BaseLayout> <BaseLayout>
<MainDiv> <MainDiv>
{children} {children}
</MainDiv> </MainDiv>
</BaseLayout> </BaseLayout>
</StoreProvider> </StoreProvider>
</I18nextProvider>
</body> </body>
</html> </html >
); );
} }
-201
View File
@@ -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;
-621
View File
@@ -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>
);
}
+7 -9
View File
@@ -2,15 +2,12 @@
import Gis from "@common/business_components/ui/map/Gis"; import Gis from "@common/business_components/ui/map/Gis";
import { useEffect, useRef, useState, useCallback } from "react"; import { useEffect, useRef, useState, useCallback } from "react";
import FeatureClickEventPopup from "../featureClickEventPopup";
import { mapService } from "../service"; import { mapService } from "../service";
import { MapSearchBar } from "../mapSearchBar";
import NewWindowPopup from "../newWindowPopup"; import NewWindowPopup from "../newWindowPopup";
import GisApp from "@common/business_components/ui/map/lib/gis.core"; import GisApp from "@common/business_components/ui/map/lib/gis.core";
const Map = () => { const Map = () => {
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태
const gisMaps = useRef<any | null>(null); const gisMaps = useRef<any | null>(null);
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
@@ -46,7 +43,7 @@ const Map = () => {
// SearchModal, GisContent 등에서 호출 // SearchModal, GisContent 등에서 호출
const onFeatureSelect = (data: any) => { const onFeatureSelect = (data: any) => {
let map = gisMaps.current; const map = gisMaps.current;
if (!map) { if (!map) {
console.warn("지도가 초기화되지 않았습니다."); console.warn("지도가 초기화되지 않았습니다.");
return; return;
@@ -60,7 +57,7 @@ const Map = () => {
select.resetSelectedFeature(); select.resetSelectedFeature();
// 2. data가 OpenLayers Feature 인스턴스인지 확인 // 2. data가 OpenLayers Feature 인스턴스인지 확인
let foundFeature = null; let foundFeature: any = null;
if ( if (
data && data &&
typeof data.getGeometry === "function" && typeof data.getGeometry === "function" &&
@@ -85,9 +82,9 @@ const Map = () => {
// 3. 피처 선택 // 3. 피처 선택
if (foundFeature) { if (foundFeature) {
let geometry = foundFeature.getGeometry(); const geometry = foundFeature.getGeometry();
if (geometry) { if (geometry) {
let extent = geometry.getExtent(); const extent = geometry.getExtent();
map.getView().fit(extent, { map.getView().fit(extent, {
duration: 300, duration: 300,
@@ -136,7 +133,7 @@ const Map = () => {
// /> // />
// ) // )
// } // }
detailView={(props: any) => ( detailView={(props: any) =>(
<NewWindowPopup <NewWindowPopup
onClose={handlePopupClose} // 메모이제이션된 함수 사용 onClose={handlePopupClose} // 메모이제이션된 함수 사용
checkedLayers={checkedLayers} checkedLayers={checkedLayers}
@@ -144,7 +141,7 @@ const Map = () => {
{...props} {...props}
/> />
)} )}
onFeatureSelect={undefined} onFeatureSelect={onFeatureSelect}
setIsOpen={setIsOpen} setIsOpen={setIsOpen}
setFeatureData={setFeatureData} setFeatureData={setFeatureData}
featureData={featureData} featureData={featureData}
@@ -152,6 +149,7 @@ const Map = () => {
isOpen={isOpen} isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen} isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange} onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
/> />
</div> </div>
); );
-414
View File
@@ -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