Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0523f6c214 | ||
|
|
71dd649624 | ||
|
|
63a0cc70af | ||
|
|
e70dec580c | ||
|
|
981d700624 | ||
|
|
9ff9e832ea | ||
|
|
b933b9149e | ||
|
|
58c435d67e | ||
|
|
950355717c | ||
|
|
823ed50f37 | ||
|
|
4d9b6235b3 | ||
|
|
500e966ec7 | ||
|
|
d7d2e2d3ef | ||
|
|
617eb217e1 | ||
|
|
db001315b7 | ||
|
|
1e6796eeb9 | ||
|
|
f15dd02cc9 | ||
|
|
e337854904 | ||
|
|
6a0572610b | ||
|
|
2850b1413f | ||
|
|
e9ccb6bcac | ||
|
|
0e516c647d | ||
|
|
98d11090f8 | ||
|
|
0a5b3cbd31 | ||
|
|
d0d713a273 | ||
|
|
20b45a02ed | ||
|
|
4c005c7b9e | ||
|
|
92356fd451 | ||
|
|
dd5af43d33 | ||
|
|
0a3a84bb98 | ||
|
|
a403113bbc | ||
|
|
e709d3cca3 | ||
|
|
8de9760099 | ||
|
|
8f3db94069 | ||
|
|
9af9bfcae8 | ||
|
|
fad4a14a82 | ||
|
|
21aab85ff8 | ||
|
|
4023423220 | ||
|
|
d20b526ff1 | ||
|
|
ae0384af83 | ||
|
|
9e5c30be8a | ||
|
|
58a2b6c5c0 | ||
|
|
e57d716559 | ||
|
|
586efe940f | ||
|
|
61f261a25b | ||
|
|
7d6f49987a |
@@ -185,6 +185,15 @@ function PostingDialog({ postingType, postingTargets }: PostingDialogProps) {
|
||||
...prev,
|
||||
[siteId]: value,
|
||||
}));
|
||||
|
||||
// 상시 선택 시 게시 기간 미설정
|
||||
if (value === "99") {
|
||||
handleUpdateFields(siteId)({
|
||||
pstgPrdSeCd: value,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setRememberPeriodDate((prev) => ({
|
||||
...prev,
|
||||
[siteId]: { from, to },
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
Skeleton,
|
||||
} from "@common/components";
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import { PostApi, type PostParams } from "@/entities/post";
|
||||
import { PostApi, PostPageGuard, type PostParams } from "@/entities/post";
|
||||
import { useAlertStore, useMenuStore, useUserStore } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import dayjs from "dayjs";
|
||||
@@ -103,143 +103,147 @@ function FaqPage({ bbsId }: { bbsId: string }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={postParams.keywordType}
|
||||
onValueChange={(keywordType) => {
|
||||
setPostParams({ ...postParams, keywordType });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="title">제목</SelectItem>
|
||||
<SelectItem value="all">제목+내용</SelectItem>
|
||||
<SelectItem value="writer">작성자</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={postParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPostParams({ ...postParams, keyword: e.target.value })
|
||||
}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
||||
<div className="flex gap-2">
|
||||
{pageMenuAuthority?.regAuthrtYn === "Y" && (
|
||||
<Button onClick={() => router.push(`${pathname}/register`)}>
|
||||
등록
|
||||
</Button>
|
||||
)}
|
||||
{pageMenuAuthority?.delAuthrtYn === "Y" && (
|
||||
<Button
|
||||
color="red"
|
||||
onClick={onSelectedPostDelete}
|
||||
disabled={!selectedPostNumbers.length}
|
||||
>
|
||||
선택삭제
|
||||
</Button>
|
||||
)}
|
||||
<PostPageGuard bbsId={bbsId}>
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={postParams.keywordType}
|
||||
onValueChange={(keywordType) => {
|
||||
setPostParams({ ...postParams, keywordType });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="title">제목</SelectItem>
|
||||
<SelectItem value="all">제목+내용</SelectItem>
|
||||
<SelectItem value="writer">작성자</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={postParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPostParams({ ...postParams, keyword: e.target.value })
|
||||
}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
||||
<div className="flex gap-2">
|
||||
{pageMenuAuthority?.regAuthrtYn === "Y" && (
|
||||
<Button onClick={() => router.push(`${pathname}/register`)}>
|
||||
등록
|
||||
</Button>
|
||||
)}
|
||||
{pageMenuAuthority?.delAuthrtYn === "Y" && (
|
||||
<Button
|
||||
color="red"
|
||||
onClick={onSelectedPostDelete}
|
||||
disabled={!selectedPostNumbers.length}
|
||||
>
|
||||
선택삭제
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
||||
<Checkbox
|
||||
id="all-select"
|
||||
checked={selectedPostNumbers.length === postList?.content.length}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={handleAllCheckboxChange}
|
||||
/>
|
||||
<Label htmlFor="all-select" className="text-2xl">
|
||||
전체선택
|
||||
</Label>
|
||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
||||
<Checkbox
|
||||
id="all-select"
|
||||
checked={
|
||||
selectedPostNumbers.length === postList?.content.length
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={handleAllCheckboxChange}
|
||||
/>
|
||||
<Label htmlFor="all-select" className="text-2xl">
|
||||
전체선택
|
||||
</Label>
|
||||
</div>
|
||||
{isFetched ? (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="secondary flex flex-col gap-4"
|
||||
>
|
||||
{postList?.content.map((item) => (
|
||||
<div key={item.pstNo} className="flex items-start gap-2">
|
||||
<Checkbox
|
||||
id={item.pstNo.toString()}
|
||||
checked={selectedPostNumbers.includes(item.pstNo)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) =>
|
||||
handleCheckboxChange(item.pstNo, checked)
|
||||
}
|
||||
className="mx-2 my-6"
|
||||
/>
|
||||
<AccordionItem
|
||||
value={item.pstNo.toString()}
|
||||
className="w-full"
|
||||
>
|
||||
<AccordionTrigger>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-primary">Q</span>
|
||||
<span>{item.pstTtl}</span>
|
||||
</div>
|
||||
<span className="px-4 text-[1.2rem] font-normal text-label">
|
||||
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
A
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
||||
{item.pstCn}
|
||||
</pre>
|
||||
<Attachment atflCd={item.atflCd} />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() =>
|
||||
router.push(`${pathname}/${item.pstNo}`)
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => handleDeletePost(item.pstNo)}
|
||||
>
|
||||
<i className="diveicon di-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
) : (
|
||||
<Skeleton className="h-[36rem] w-full" />
|
||||
)}
|
||||
</div>
|
||||
{isFetched ? (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="secondary flex flex-col gap-4"
|
||||
>
|
||||
{postList?.content.map((item) => (
|
||||
<div key={item.pstNo} className="flex items-start gap-2">
|
||||
<Checkbox
|
||||
id={item.pstNo.toString()}
|
||||
checked={selectedPostNumbers.includes(item.pstNo)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) =>
|
||||
handleCheckboxChange(item.pstNo, checked)
|
||||
}
|
||||
className="mx-2 my-6"
|
||||
/>
|
||||
<AccordionItem
|
||||
value={item.pstNo.toString()}
|
||||
className="w-full"
|
||||
>
|
||||
<AccordionTrigger>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-primary">Q</span>
|
||||
<span>{item.pstTtl}</span>
|
||||
</div>
|
||||
<span className="px-4 text-[1.2rem] font-normal text-label">
|
||||
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
A
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
||||
{item.pstCn}
|
||||
</pre>
|
||||
<Attachment atflCd={item.atflCd} />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() =>
|
||||
router.push(`${pathname}/${item.pstNo}`)
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => handleDeletePost(item.pstNo)}
|
||||
>
|
||||
<i className="diveicon di-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
) : (
|
||||
<Skeleton className="h-[36rem] w-full" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PostPageGuard>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+27
-8
@@ -1,5 +1,12 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config";
|
||||
import {
|
||||
CLAIM_NAME,
|
||||
AUTH_USER_ID,
|
||||
PORT,
|
||||
PROXY_HOST,
|
||||
SERVER_API_URL,
|
||||
SITE_ID,
|
||||
} from "./src/shared/config";
|
||||
|
||||
const serverApiUrl =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL ?? "http://localhost:3000";
|
||||
@@ -7,17 +14,23 @@ const serverApiUrl =
|
||||
export function middleware(req: NextRequest) {
|
||||
const pathname = req.nextUrl.pathname;
|
||||
const searchParams = req.nextUrl.searchParams;
|
||||
|
||||
|
||||
const logMessages: string[] = [
|
||||
`[Middleware Request] ${new Date().toISOString()}`,
|
||||
`[Environment]\n Mode: ${process.env.NODE_ENV}\n Port: ${PORT}\n Proxy Host: ${PROXY_HOST}\n Server API URL: ${SERVER_API_URL}\n Site ID: ${SITE_ID}`,
|
||||
`[Request]\n Original Pathname: ${pathname}`,
|
||||
];
|
||||
|
||||
if (pathname.startsWith("/server")) {
|
||||
|
||||
const decodedParams = Array.from(searchParams.entries())
|
||||
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
|
||||
.join("&");
|
||||
|
||||
const queryParams = searchParams.toString();
|
||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||
decodedParams ? `?${decodedParams}` : ""
|
||||
queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
@@ -30,6 +43,10 @@ export function middleware(req: NextRequest) {
|
||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||
|
||||
logMessages.push(` Rewritten URL: ${newUrl}`);
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
|
||||
return NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders,
|
||||
});
|
||||
@@ -39,6 +56,8 @@ export function middleware(req: NextRequest) {
|
||||
return NextResponse.redirect(new URL("/dashboard", req.url));
|
||||
}
|
||||
|
||||
logMessages.push("\n--------------------------------\n");
|
||||
console.log(logMessages.join("\n"));
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,11 +67,22 @@ export const contentsDetailSchema = z.object({
|
||||
useYn: z.string(),
|
||||
});
|
||||
|
||||
export const contentsFormSchema = contentsDetailSchema.omit({
|
||||
contsVer: true,
|
||||
aprvDt: true,
|
||||
autzr: true,
|
||||
});
|
||||
export const contentsFormSchema = contentsDetailSchema
|
||||
.omit({
|
||||
contsVer: true,
|
||||
aprvDt: true,
|
||||
autzr: true,
|
||||
})
|
||||
.extend({
|
||||
contsSn: z.number().optional(),
|
||||
aprvCd: z
|
||||
.enum([
|
||||
CONTENTS_APPROVE_STATUS.DRAFT,
|
||||
CONTENTS_APPROVE_STATUS.REQUESTED,
|
||||
CONTENTS_APPROVE_STATUS.APPROVED,
|
||||
])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type Contents = z.infer<typeof contentsSchema>;
|
||||
export type ContentsDetail = z.infer<typeof contentsDetailSchema>;
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
MenuItem,
|
||||
MenuFormData,
|
||||
MenuTemporarilyStoredData,
|
||||
SiteMapFormData,
|
||||
} from "../model/menu-interface";
|
||||
|
||||
export class MenuApiService implements MenuService {
|
||||
@@ -65,6 +66,14 @@ export class MenuApiService implements MenuService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async publishSiteMap(data: SiteMapFormData["siteMap"]): Promise<void> {
|
||||
const response = await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.MENUS}/sitemap/publish`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const MenuApi = new MenuApiService();
|
||||
|
||||
@@ -2,8 +2,11 @@ export { MenuApi } from "./api/menu-service";
|
||||
export {
|
||||
MENU_TYPE,
|
||||
menuFormSchema,
|
||||
siteMapFormSchema,
|
||||
type MenuType,
|
||||
type MenuItem,
|
||||
type MenuFormData,
|
||||
type SiteMapFormData,
|
||||
type SiteMapPayloadData,
|
||||
} from "./model/menu-interface";
|
||||
export { MenuWrapper } from "./lib/menu-wrapper";
|
||||
|
||||
@@ -72,8 +72,19 @@ export const menuFormSchema = z.object({
|
||||
menuPicDsgnYmd: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type MenuFormData = z.infer<typeof menuFormSchema>;
|
||||
export const siteMapPayloadSchema = z.object({
|
||||
menuCd: z.number(),
|
||||
menuSubKornNm: z.string(),
|
||||
otptYn: z.string(),
|
||||
});
|
||||
|
||||
export const siteMapFormSchema = z.object({
|
||||
siteMap: z.array(siteMapPayloadSchema),
|
||||
});
|
||||
|
||||
export type MenuFormData = z.infer<typeof menuFormSchema>;
|
||||
export type SiteMapFormData = z.infer<typeof siteMapFormSchema>;
|
||||
export type SiteMapPayloadData = z.infer<typeof siteMapPayloadSchema>;
|
||||
export type Menu = Omit<MenuFormData, "iconNm"> & {
|
||||
menuCd: number;
|
||||
iconName?: string;
|
||||
@@ -92,4 +103,5 @@ export interface MenuService {
|
||||
getMenuTemporarilyStoredData: (
|
||||
menuCd: number,
|
||||
) => Promise<MenuTemporarilyStoredData[]>;
|
||||
publishSiteMap: (data: SiteMapFormData["siteMap"]) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -194,6 +194,15 @@ function PostingDialog({
|
||||
...prev,
|
||||
[siteId]: value,
|
||||
}));
|
||||
|
||||
// 상시 선택 시 게시 기간 미설정
|
||||
if (value === "99") {
|
||||
handleUpdateFields(siteId)({
|
||||
pstgPrdSeCd: value,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setRememberPeriodDate((prev) => ({
|
||||
...prev,
|
||||
[siteId]: { from, to },
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { TreeMenuApi } from "./api/tree-menu-service";
|
||||
export type { TreeMenu, TreeNode } from "./model/tree-menu-interface";
|
||||
+1
@@ -19,6 +19,7 @@ export interface TreeMenu extends Omit<TreeNode, "name" | "icon" | "children"> {
|
||||
qrcd?: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
menuSubKornNm?: string;
|
||||
requiredUrlPath?: boolean;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export {
|
||||
userSaveSchema,
|
||||
userPasswordUpdateSchema,
|
||||
userUpdateSchema,
|
||||
type UserStateCode,
|
||||
type UserSearchParams,
|
||||
type UserListResponse,
|
||||
type UserSaveData,
|
||||
|
||||
@@ -9,7 +9,8 @@ export const USER_STATE_CODE = {
|
||||
삭제: "09",
|
||||
} as const;
|
||||
|
||||
type UserStateCode = (typeof USER_STATE_CODE)[keyof typeof USER_STATE_CODE];
|
||||
export type UserStateCode =
|
||||
(typeof USER_STATE_CODE)[keyof typeof USER_STATE_CODE];
|
||||
|
||||
// 사용자 검색 파라미터
|
||||
export interface UserSearchParams extends PageableParams {
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { AttachmentApi } from "@/entities/attachment";
|
||||
import {
|
||||
PostApi,
|
||||
PostPageGuard,
|
||||
deleteReasonMessages,
|
||||
type PostParams,
|
||||
} from "@/entities/post";
|
||||
@@ -120,143 +121,147 @@ function FaqPage({ bbsId }: { bbsId: string }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={postParams.keywordType}
|
||||
onValueChange={(keywordType) => {
|
||||
setPostParams({ ...postParams, keywordType });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="title">제목</SelectItem>
|
||||
<SelectItem value="all">제목+내용</SelectItem>
|
||||
<SelectItem value="writer">작성자</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={postParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPostParams({ ...postParams, keyword: e.target.value })
|
||||
}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
||||
<div className="flex gap-2">
|
||||
<AuthorityButton
|
||||
authority="regAuthrtYn"
|
||||
onClick={() => router.push(`${pathname}/register`)}
|
||||
>
|
||||
등록
|
||||
</AuthorityButton>
|
||||
<AuthorityButton
|
||||
authority="delAuthrtYn"
|
||||
color="red"
|
||||
onClick={onSelectedPostDelete}
|
||||
disabled={!selectedPostNumbers.length}
|
||||
>
|
||||
선택삭제
|
||||
</AuthorityButton>
|
||||
<PostPageGuard bbsId={bbsId}>
|
||||
<div className="card card-border !mt-6">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={postParams.keywordType}
|
||||
onValueChange={(keywordType) => {
|
||||
setPostParams({ ...postParams, keywordType });
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-[3.2rem] w-[10rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="title">제목</SelectItem>
|
||||
<SelectItem value="all">제목+내용</SelectItem>
|
||||
<SelectItem value="writer">작성자</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={postParams.keyword}
|
||||
onChange={(e) =>
|
||||
setPostParams({ ...postParams, keyword: e.target.value })
|
||||
}
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
/>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<h3 className="text-2xl font-medium text-label">콘텐츠 목록</h3>
|
||||
<div className="flex gap-2">
|
||||
<AuthorityButton
|
||||
authority="regAuthrtYn"
|
||||
onClick={() => router.push(`${pathname}/register`)}
|
||||
>
|
||||
등록
|
||||
</AuthorityButton>
|
||||
<AuthorityButton
|
||||
authority="delAuthrtYn"
|
||||
color="red"
|
||||
onClick={onSelectedPostDelete}
|
||||
disabled={!selectedPostNumbers.length}
|
||||
>
|
||||
선택삭제
|
||||
</AuthorityButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
||||
<Checkbox
|
||||
id="all-select"
|
||||
checked={selectedPostNumbers.length === postList?.content.length}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={handleAllCheckboxChange}
|
||||
/>
|
||||
<Label htmlFor="all-select" className="text-2xl">
|
||||
전체선택
|
||||
</Label>
|
||||
<div className="card !m-[1.2rem] h-full overflow-auto !p-0">
|
||||
<div className="mb-4 flex items-center gap-2 rounded-sm border border-border bg-background p-4">
|
||||
<Checkbox
|
||||
id="all-select"
|
||||
checked={
|
||||
selectedPostNumbers.length === postList?.content.length
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={handleAllCheckboxChange}
|
||||
/>
|
||||
<Label htmlFor="all-select" className="text-2xl">
|
||||
전체선택
|
||||
</Label>
|
||||
</div>
|
||||
{isFetched ? (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="secondary flex flex-col gap-4"
|
||||
>
|
||||
{postList?.content.map((item) => (
|
||||
<div key={item.pstNo} className="flex items-start gap-2">
|
||||
<Checkbox
|
||||
id={item.pstNo.toString()}
|
||||
checked={selectedPostNumbers.includes(item.pstNo)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) =>
|
||||
handleCheckboxChange(item.pstNo, checked)
|
||||
}
|
||||
className="mx-2 my-6"
|
||||
/>
|
||||
<AccordionItem
|
||||
value={item.pstNo.toString()}
|
||||
className="w-full"
|
||||
>
|
||||
<AccordionTrigger>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-primary">Q</span>
|
||||
<span>{item.pstTtl}</span>
|
||||
</div>
|
||||
<span className="px-4 text-[1.2rem] font-normal text-label">
|
||||
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
A
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
||||
{item.pstCn}
|
||||
</pre>
|
||||
<Attachment atflCd={item.atflCd} />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() =>
|
||||
router.push(`${pathname}/${item.pstNo}`)
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => handleDeletePost(item.pstNo)}
|
||||
>
|
||||
<i className="diveicon di-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
) : (
|
||||
<Skeleton className="h-[36rem] w-full" />
|
||||
)}
|
||||
</div>
|
||||
{isFetched ? (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="secondary flex flex-col gap-4"
|
||||
>
|
||||
{postList?.content.map((item) => (
|
||||
<div key={item.pstNo} className="flex items-start gap-2">
|
||||
<Checkbox
|
||||
id={item.pstNo.toString()}
|
||||
checked={selectedPostNumbers.includes(item.pstNo)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={(checked) =>
|
||||
handleCheckboxChange(item.pstNo, checked)
|
||||
}
|
||||
className="mx-2 my-6"
|
||||
/>
|
||||
<AccordionItem
|
||||
value={item.pstNo.toString()}
|
||||
className="w-full"
|
||||
>
|
||||
<AccordionTrigger>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-primary">Q</span>
|
||||
<span>{item.pstTtl}</span>
|
||||
</div>
|
||||
<span className="px-4 text-[1.2rem] font-normal text-label">
|
||||
등록일: {dayjs(item.regDt).format("YYYY-MM-DD")}
|
||||
</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex items-start gap-4">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
A
|
||||
</span>
|
||||
<div className="flex-1">
|
||||
<pre className="whitespace-pre-wrap font-noto text-[1.4rem] tracking-tighter text-foreground">
|
||||
{item.pstCn}
|
||||
</pre>
|
||||
<Attachment atflCd={item.atflCd} />
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() =>
|
||||
router.push(`${pathname}/${item.pstNo}`)
|
||||
}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
color="white"
|
||||
onClick={() => handleDeletePost(item.pstNo)}
|
||||
>
|
||||
<i className="diveicon di-trash" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
) : (
|
||||
<Skeleton className="h-[36rem] w-full" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PostPageGuard>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ function AdminPage() {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const [keywordType, setKeywordType] = useState("");
|
||||
const [keywordType, setKeywordType] = useState("userId");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [grpId, setGrpId] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<UserSearchParams>({
|
||||
|
||||
@@ -15,8 +15,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import { ApiLogApi } from "@/entities/apilog";
|
||||
import { type ApiLogParams } from "@/entities/apilog/model/apilog-interface";
|
||||
import { ApiLogApi, type ApiLogParams } from "@/entities/apilog";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/apilog/model/table-columns";
|
||||
|
||||
@@ -35,9 +35,14 @@ export const columns: ColumnDef<Post>[] = [
|
||||
accessorKey: "pstgPrdSeNm",
|
||||
header: "게시기간",
|
||||
cell: ({ row }) => {
|
||||
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||
const pstgBgngDt = row.original.pstgBgngDt;
|
||||
const pstgEndDt = row.original.pstgEndDt;
|
||||
|
||||
if (pstgPrdSeCd === "99") {
|
||||
return pstgPrdSeNm;
|
||||
}
|
||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -34,7 +34,7 @@ function CreatePage() {
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">콘텐츠 등록</h3>
|
||||
</div>
|
||||
<ContentsForm form={form} handleSave={createContents} />
|
||||
<ContentsForm isEditable form={form} handleSave={createContents} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
@@ -24,6 +25,7 @@ import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { ContentsForm } from "./contents-form";
|
||||
|
||||
function ModifyPage({ sequential }: { sequential: string }) {
|
||||
const router = useRouter();
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
@@ -51,9 +53,11 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
||||
mutationFn: (data: ContentsFormData) => ContentsApi.createContents(data),
|
||||
onSuccess: () => {
|
||||
setContsVer(0);
|
||||
alert("수정되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
||||
alert("수정되었습니다.", () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
||||
});
|
||||
router.back();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -62,7 +66,7 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
||||
mutationFn: (data: ContentsFormData) =>
|
||||
ContentsApi.updateContents(contsSn, contsVer, data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.");
|
||||
alert("수정되었습니다.", router.back);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -70,9 +74,11 @@ function ModifyPage({ sequential }: { sequential: string }) {
|
||||
mutationFn: () => ContentsApi.deleteContents(contsSn, contsVer),
|
||||
onSuccess: () => {
|
||||
setContsVer(0);
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
||||
alert("삭제되었습니다.", () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn],
|
||||
});
|
||||
router.back();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -39,8 +39,7 @@ function FilePage() {
|
||||
mutationFn: (atflId: string) =>
|
||||
AttachmentApi.deleteAttachmentByAtflId(atflId),
|
||||
onSuccess: () => {
|
||||
query.refetch();
|
||||
alert("삭제되었습니다.");
|
||||
alert("삭제되었습니다.", query.refetch);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
keepPreviousData,
|
||||
useQuery,
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
DialogFooter,
|
||||
Input,
|
||||
Checkbox,
|
||||
Label,
|
||||
} from "@common/components";
|
||||
import {
|
||||
UserApi,
|
||||
@@ -41,7 +42,8 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [userSearchQuery, setUserSearchQuery] = useState<UserSearchParams>({
|
||||
const [userName, setUserName] = useState("");
|
||||
const [searchParam, setSearchParam] = useState<UserSearchParams>({
|
||||
userName: "",
|
||||
});
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
@@ -53,10 +55,10 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
||||
);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, userSearchQuery, pagination],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS, searchParam, pagination],
|
||||
queryFn: () =>
|
||||
UserApi.findPageUser({
|
||||
...userSearchQuery,
|
||||
...searchParam,
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
}),
|
||||
@@ -118,6 +120,9 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
||||
onRowSelectionChange: setUserRowSelection,
|
||||
getRowId: (row) => row.userUnqId,
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const handleAddUsers = async () => {
|
||||
@@ -144,10 +149,30 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
setPagination({ pageIndex: 0, pageSize: 10 });
|
||||
const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setPagination({
|
||||
...pagination,
|
||||
pageIndex: 0,
|
||||
});
|
||||
setSearchParam({ userName });
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
setPagination({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
setUserName("");
|
||||
setSearchParam({ userName: "" });
|
||||
setUserRowSelection({});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) resetSearch();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
@@ -157,20 +182,31 @@ function AddUserDialog({ groupId }: AdditionalUserDialogProps) {
|
||||
<DialogHeader>
|
||||
<DialogTitle>사용자 추가</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="mb-4 flex items-end gap-2">
|
||||
<Input
|
||||
value={userSearchQuery.userName}
|
||||
onChange={(e) =>
|
||||
setUserSearchQuery({
|
||||
...userSearchQuery,
|
||||
userName: e.target.value,
|
||||
})
|
||||
}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="사용자 이름으로 검색"
|
||||
/>
|
||||
<Button onClick={handleSearch}>검색</Button>
|
||||
</div>
|
||||
<form onSubmit={handleSearch} className="mb-4 flex items-end gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="userName" className="mr-2 text-label">
|
||||
사용자 이름
|
||||
</Label>
|
||||
<Input
|
||||
id="userName"
|
||||
className="h-[3.2rem] w-[24rem] rounded-sm !text-[1.3rem]"
|
||||
value={userName}
|
||||
onChange={(e) => setUserName(e.target.value)}
|
||||
placeholder="사용자 이름으로 검색"
|
||||
/>
|
||||
</div>
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={resetSearch}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
{tableComponent}
|
||||
<DialogFooter>
|
||||
<Button color="white" onClick={() => setOpen(false)}>
|
||||
|
||||
@@ -35,9 +35,14 @@ export const columns: ColumnDef<Post>[] = [
|
||||
accessorKey: "pstgPrdSeNm",
|
||||
header: "게시기간",
|
||||
cell: ({ row }) => {
|
||||
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||
const pstgBgngDt = row.original.pstgBgngDt;
|
||||
const pstgEndDt = row.original.pstgEndDt;
|
||||
|
||||
if (pstgPrdSeCd === "99") {
|
||||
return pstgPrdSeNm;
|
||||
}
|
||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -16,8 +16,7 @@ import {
|
||||
SelectValue,
|
||||
LoadingWithImage,
|
||||
} from "@common/components";
|
||||
import { LoginlogApi } from "@/entities/loginlog";
|
||||
import { type LoginlogParams } from "@/entities/loginlog/model/loginlog-interface";
|
||||
import { LoginlogApi, type LoginlogParams } from "@/entities/loginlog";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/loginlog/model/table-columns";
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
TreeUserOrganization,
|
||||
TreeUser,
|
||||
} from "@/entities/user-organization";
|
||||
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
|
||||
import type { TreeMenu, TreeNode } from "@/entities/tree-menu";
|
||||
|
||||
export const generateMenuTreeData = (
|
||||
data: TreeMenu[],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
|
||||
type EditableNodeProps = {
|
||||
depth?: number;
|
||||
@@ -20,22 +21,34 @@ function EditableNode({
|
||||
>
|
||||
<Input {...props} className="h-[2.5rem] !text-[1.3rem] text-foreground" />
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 !bg-transparent"
|
||||
onClick={onSave}
|
||||
>
|
||||
<i className="diveicon di-check text-2xl" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 !bg-transparent"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<i className="diveicon di-x" />
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 !bg-transparent"
|
||||
onClick={onSave}
|
||||
aria-label="저장"
|
||||
>
|
||||
<i className="diveicon di-check text-2xl" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>저장</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 !bg-transparent"
|
||||
onClick={onCancel}
|
||||
aria-label="취소"
|
||||
>
|
||||
<i className="diveicon di-x" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>취소</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,10 +2,9 @@ import { useEffect, useState } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview";
|
||||
import { Button, cn } from "@common/components";
|
||||
import { TreeMenuApi, type TreeMenu } from "@/entities/tree-menu";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu } from "../model/tree-menu-interface";
|
||||
import { EditableNode } from "./editable-node";
|
||||
|
||||
type MenuNodeProps = {
|
||||
|
||||
@@ -21,11 +21,10 @@ import {
|
||||
type MenuFormData,
|
||||
} from "@/entities/menu";
|
||||
import { SiteApi } from "@/entities/site";
|
||||
import { TreeMenuApi, type TreeMenu } from "@/entities/tree-menu";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { generateMenuTreeData } from "../lib/utils";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu } from "../model/tree-menu-interface";
|
||||
import { MenuTree } from "./menu-tree";
|
||||
import { MenuForm } from "./menu-form";
|
||||
|
||||
|
||||
@@ -12,8 +12,11 @@ import { Button } from "@common/components";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useMutationWrapper } from "@/shared/lib";
|
||||
import type { MenuFormData } from "@/entities/menu";
|
||||
import { TreeMenuApi } from "../api/tree-menu-service";
|
||||
import type { TreeMenu, TreeNode } from "../model/tree-menu-interface";
|
||||
import {
|
||||
TreeMenuApi,
|
||||
type TreeMenu,
|
||||
type TreeNode,
|
||||
} from "@/entities/tree-menu";
|
||||
import { handleDropNode, handleSelectNode } from "../lib/utils";
|
||||
import { MenuNode } from "./menu-node";
|
||||
import { EditableNode } from "./editable-node";
|
||||
|
||||
@@ -72,7 +72,7 @@ function AddUserDialog({ onAddUsers, ...props }: AddUserDialogProps) {
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>콘텐츠 조회</DialogTitle>
|
||||
<DialogTitle>사용자 조회</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="card card-border !m-0 flex w-full items-center gap-2">
|
||||
|
||||
@@ -242,6 +242,16 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
||||
</div>
|
||||
<div className="card max-h-[calc(100vh-58rem)] overflow-auto !p-0">
|
||||
<Table>
|
||||
<colgroup>
|
||||
<col width="5%" />
|
||||
<col width="5%" />
|
||||
<col width="auto" />
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
<col width="15%" />
|
||||
<col width="15%" />
|
||||
</colgroup>
|
||||
<TableHeader className="sticky top-0">
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
@@ -300,6 +310,9 @@ const UserCells = memo(
|
||||
jobPositionCodeData,
|
||||
jobGradeCodeData,
|
||||
}: UserCellsProps) => {
|
||||
const [jbpsSeCd, setJbpsSeCd] = useState(user.jbpsSeCd);
|
||||
const [jbgdSeCd, setJbgdSeCd] = useState(user.jbgdSeCd);
|
||||
|
||||
const updateMode = (index: number) => {
|
||||
const mode = form.watch(`userOrganization.${index}.mode`);
|
||||
if (mode === "R") {
|
||||
@@ -322,17 +335,33 @@ const UserCells = memo(
|
||||
<CodeSelect
|
||||
codeData={jobPositionCodeData}
|
||||
defaultValue={user.jbpsSeCd}
|
||||
onValueChange={updateFormValue(index, "jbpsSeCd")}
|
||||
onValueChange={(value) => {
|
||||
setJbpsSeCd(value);
|
||||
updateFormValue(index, "jbpsSeCd")(value);
|
||||
}}
|
||||
{...form.register(`userOrganization.${index}.jbpsSeCd`)}
|
||||
/>
|
||||
{!jbpsSeCd && (
|
||||
<p className="pt-1 text-base text-destructive">
|
||||
직위를 선택해주세요.
|
||||
</p>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<CodeSelect
|
||||
codeData={jobGradeCodeData}
|
||||
defaultValue={user.jbgdSeCd}
|
||||
onValueChange={updateFormValue(index, "jbgdSeCd")}
|
||||
onValueChange={(value) => {
|
||||
setJbgdSeCd(value);
|
||||
updateFormValue(index, "jbgdSeCd")(value);
|
||||
}}
|
||||
{...form.register(`userOrganization.${index}.jbgdSeCd`)}
|
||||
/>
|
||||
{!jbgdSeCd && (
|
||||
<p className="pt-1 text-base text-destructive">
|
||||
직급을 선택해주세요.
|
||||
</p>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<Input
|
||||
|
||||
@@ -33,9 +33,14 @@ export const columns: ColumnDef<Post>[] = [
|
||||
accessorKey: "pstgPrdSeNm",
|
||||
header: "게시기간",
|
||||
cell: ({ row }) => {
|
||||
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||
const pstgBgngDt = row.original.pstgBgngDt;
|
||||
const pstgEndDt = row.original.pstgEndDt;
|
||||
|
||||
if (pstgPrdSeCd === "99") {
|
||||
return pstgPrdSeNm;
|
||||
}
|
||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -15,8 +15,10 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import { PrivacyLogPrivacy } from "@/entities/privacylog";
|
||||
import type { PrivacyLogParams } from "@/entities/privacylog/model/privacylog-interface";
|
||||
import {
|
||||
PrivacyLogPrivacy,
|
||||
type PrivacyLogParams,
|
||||
} from "@/entities/privacylog";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/privacylog/model/table-columns";
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { client } from "@/shared/api";
|
||||
import type {
|
||||
SiteAuthorityCreatePayload,
|
||||
SiteAuthorityList,
|
||||
SiteAuthoritySearchParams,
|
||||
SiteAuthorityService,
|
||||
} from "../model/site-authority-interface";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
export class SiteAuthorityApiService implements SiteAuthorityService {
|
||||
public async getSiteAuthorityList(
|
||||
params: SiteAuthoritySearchParams,
|
||||
): Promise<SiteAuthorityList> {
|
||||
const response = await client.get<SiteAuthorityList>(
|
||||
ENDPOINT.CMS_SERVICE.SITE_AUTHORITY,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createSiteAuthority(
|
||||
data: SiteAuthorityCreatePayload[],
|
||||
): Promise<void> {
|
||||
await client.post(ENDPOINT.CMS_SERVICE.SITE_AUTHORITY, data);
|
||||
}
|
||||
}
|
||||
|
||||
export const SiteAuthorityApi = new SiteAuthorityApiService();
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { PageableList, PageableParams } from "@/shared/model";
|
||||
|
||||
export interface SiteAuthoritySearchParams extends PageableParams {
|
||||
siteId?: string;
|
||||
authrtId?: string;
|
||||
}
|
||||
|
||||
export interface SiteAuthority {
|
||||
siteId: string;
|
||||
authrtId: string;
|
||||
siteNm: string;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export type SiteAuthorityCreatePayload = Omit<SiteAuthority, "siteNm">;
|
||||
|
||||
export type SiteAuthorityList = PageableList<SiteAuthority>;
|
||||
|
||||
export interface SiteAuthorityService {
|
||||
getSiteAuthorityList: (
|
||||
params: SiteAuthoritySearchParams,
|
||||
) => Promise<SiteAuthorityList>;
|
||||
createSiteAuthority: (data: SiteAuthorityCreatePayload[]) => Promise<void>;
|
||||
}
|
||||
@@ -3,8 +3,13 @@ import { useState } from "react";
|
||||
import type { Role } from "@/entities/role";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { MappingRoleDialog } from "../ui/mapping-role-dialog";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
export const columns: ColumnDef<Role>[] = [
|
||||
export const createColumns = ({
|
||||
deleteRole,
|
||||
}: {
|
||||
deleteRole: (roleId: string) => void;
|
||||
}): ColumnDef<Role>[] => [
|
||||
{
|
||||
id: "no",
|
||||
header: "NO",
|
||||
@@ -39,7 +44,18 @@ export const columns: ColumnDef<Role>[] = [
|
||||
{
|
||||
id: "action",
|
||||
header: "관리",
|
||||
cell: ({ row }) => <MappingRoleButton roleId={row.original.roleId} />,
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<MappingRoleButton roleId={row.original.roleId} />
|
||||
<Button
|
||||
size="sm"
|
||||
color="red"
|
||||
onClick={() => deleteRole(row.original.roleId)}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getCoreRowModel, type ColumnDef } from "@tanstack/react-table";
|
||||
import { useState, useRef } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type ColumnDef } from "@tanstack/react-table";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
Button,
|
||||
Switch,
|
||||
Input,
|
||||
} from "@common/components";
|
||||
import {
|
||||
RoleAuthorizationsApi,
|
||||
@@ -31,12 +33,18 @@ function MappingRoleDialog({
|
||||
}: MappingRoleDialogProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
keyword: "",
|
||||
keywordType: "authorizationName",
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.ROLE_AUTHORIZATIONS, searchParams],
|
||||
queryFn: () =>
|
||||
RoleAuthorizationsApi.getRoleAuthorizationsListByRoleId({
|
||||
roleId,
|
||||
...searchParams,
|
||||
}),
|
||||
enabled: open,
|
||||
});
|
||||
@@ -46,7 +54,7 @@ function MappingRoleDialog({
|
||||
RoleAuthorizationsApi.createRoleAuthorizations(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.AUTHORIZATIONS],
|
||||
queryKey: [ENDPOINT.USER_SERVICE.ROLE_AUTHORIZATIONS],
|
||||
});
|
||||
alert("권한 맵핑이 완료되었습니다.");
|
||||
},
|
||||
@@ -66,6 +74,26 @@ function MappingRoleDialog({
|
||||
);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const keyword = formData.get("keyword") as string;
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = "";
|
||||
}
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
});
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
@@ -79,21 +107,31 @@ function MappingRoleDialog({
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">권한맵핑</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
|
||||
<DialogContent className="max-w-[80rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>모듈 목록</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[40rem] overflow-y-auto">
|
||||
<div className="mb-4 flex justify-end gap-2">
|
||||
<Button size="sm" onClick={() => handleApplyAll("Y")}>
|
||||
일괄적용
|
||||
<div className="flex items-center justify-between">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
name="keyword"
|
||||
className="w-[32rem]"
|
||||
/>
|
||||
<Button type="submit">검색</Button>
|
||||
<Button type="button" color="white" onClick={handleReset}>
|
||||
초기화
|
||||
</Button>
|
||||
<Button size="sm" color="gray" onClick={() => handleApplyAll("N")}>
|
||||
</form>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<Button onClick={() => handleApplyAll("Y")}>일괄적용</Button>
|
||||
<Button color="gray" onClick={() => handleApplyAll("N")}>
|
||||
일괄해제
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
</div>
|
||||
<div className="max-h-[60rem] overflow-y-auto">{tableComponent}</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
@@ -16,6 +18,9 @@ import { FormWrapper } from "@/widgets/form-wrapper";
|
||||
import { RoleApi, type RoleFormData } from "@/entities/role";
|
||||
import { FormField, FormItem, FormControl } from "@/shared/ui/form";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { SiteAuthorityApi } from "../api/site-authority-service";
|
||||
import type { SiteAuthorityCreatePayload } from "../model/site-authority-interface";
|
||||
|
||||
type RoleFormProps = {
|
||||
roleId?: string;
|
||||
@@ -24,12 +29,24 @@ type RoleFormProps = {
|
||||
handleDelete?: () => Promise<void>;
|
||||
};
|
||||
|
||||
function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
function RoleForm({ roleId, form, handleSave, ...props }: RoleFormProps) {
|
||||
const isModifyMode = !!roleId;
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [isValidRoleId, setIsValidRoleId] = useState(false);
|
||||
const [checkedSiteAuthority, setCheckedSiteAuthority] = useState<string[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const { data: siteAuthorityList } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SITE_AUTHORITY, roleId],
|
||||
queryFn: () =>
|
||||
SiteAuthorityApi.getSiteAuthorityList({
|
||||
authrtId: roleId,
|
||||
page: 0,
|
||||
size: 100,
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: checkDuplicateRoleId } = useMutationWrapper({
|
||||
mutationFn: (roleId: string) => RoleApi.checkDuplicateRoleId(roleId),
|
||||
@@ -43,8 +60,49 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
},
|
||||
});
|
||||
|
||||
const handleSiteAuthorityChecked = (checked: boolean, siteId: string) => {
|
||||
if (checked) {
|
||||
setCheckedSiteAuthority((prev) => [...prev, siteId]);
|
||||
} else {
|
||||
setCheckedSiteAuthority((prev) => prev.filter((id) => id !== siteId));
|
||||
}
|
||||
};
|
||||
|
||||
const saveInterceptor = async (data: RoleFormData) => {
|
||||
const siteAuthorityPayload: SiteAuthorityCreatePayload[] =
|
||||
checkedSiteAuthority.map((siteId) => ({
|
||||
siteId,
|
||||
authrtId: data.roleId,
|
||||
mode: "C",
|
||||
}));
|
||||
|
||||
try {
|
||||
await handleSave(data);
|
||||
if (data.mngrYn === "N") return;
|
||||
await SiteAuthorityApi.createSiteAuthority(siteAuthorityPayload);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.SITE_AUTHORITY],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!siteAuthorityList?.content.length) return;
|
||||
const checkedSiteAuthority = siteAuthorityList.content.filter(
|
||||
(item) => item.mode === "R",
|
||||
);
|
||||
setCheckedSiteAuthority(checkedSiteAuthority.map((item) => item.siteId));
|
||||
}, [siteAuthorityList]);
|
||||
|
||||
return (
|
||||
<FormWrapper form={form} isModifyMode={isModifyMode} {...props}>
|
||||
<FormWrapper
|
||||
form={form}
|
||||
isModifyMode={isModifyMode}
|
||||
handleSave={saveInterceptor}
|
||||
{...props}
|
||||
>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
@@ -165,6 +223,29 @@ function RoleForm({ roleId, form, ...props }: RoleFormProps) {
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{form.watch("mngrYn") === "Y" && (
|
||||
<TableRow>
|
||||
<TableHead data-required>관리사이트</TableHead>
|
||||
<TableCell className="border">
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{siteAuthorityList?.content.map((item) => (
|
||||
<div
|
||||
key={item.siteId}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Checkbox
|
||||
defaultChecked={item.mode === "R"}
|
||||
onCheckedChange={(checked: boolean) => {
|
||||
handleSiteAuthorityChecked(checked, item.siteId);
|
||||
}}
|
||||
/>
|
||||
<span className="whitespace-nowrap">{item.siteNm}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableHead data-required>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
|
||||
@@ -7,11 +7,12 @@ import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import { Button, Input, Label } from "@common/components";
|
||||
import { RoleApi, type Role, type RoleParams } from "@/entities/role";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { columns } from "../model/table-columns";
|
||||
import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib";
|
||||
import { createColumns } from "../model/table-columns";
|
||||
|
||||
function RolePage() {
|
||||
const router = useRouter();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<RoleParams>({
|
||||
@@ -33,6 +34,13 @@ function RolePage() {
|
||||
}),
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteRole } = useMutationWrapper({
|
||||
mutationFn: (roleId: string) => RoleApi.deleteRole(roleId),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.", query.refetch);
|
||||
},
|
||||
});
|
||||
|
||||
const handleRowClick = (row: Role) => {
|
||||
router.push(`/role/register/${row.roleId}`);
|
||||
};
|
||||
@@ -40,7 +48,9 @@ function RolePage() {
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
columns: createColumns({
|
||||
deleteRole,
|
||||
}),
|
||||
state: {
|
||||
pagination,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { NodeModel } from "@minoru/react-dnd-treeview";
|
||||
import type { TreeMenu } from "@/entities/tree-menu";
|
||||
|
||||
export const generateMenuTreeData = (
|
||||
data: TreeMenu[],
|
||||
): NodeModel<TreeMenu>[] => {
|
||||
return data.reduce<NodeModel<TreeMenu>[]>((acc, menu) => {
|
||||
const node: NodeModel<TreeMenu> = {
|
||||
id: menu.menuCd ?? 0,
|
||||
parent: menu.parentId ?? 0,
|
||||
text: menu.menuSubKornNm ?? "",
|
||||
droppable: true,
|
||||
data: menu,
|
||||
};
|
||||
|
||||
acc.push(node);
|
||||
|
||||
if (menu.children?.length) {
|
||||
acc.push(...generateMenuTreeData(menu.children));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
@@ -1,6 +1,32 @@
|
||||
import dayjs from "dayjs";
|
||||
import { memo, useState } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type ColumnDef } from "@tanstack/react-table";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@common/components";
|
||||
import type { Site } from "@/entities/site";
|
||||
import { TreeMenuApi } from "@/entities/tree-menu";
|
||||
import {
|
||||
MenuApi,
|
||||
siteMapFormSchema,
|
||||
type SiteMapFormData,
|
||||
} from "@/entities/menu";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { MenuTree } from "../ui/menu-tree";
|
||||
import { generateMenuTreeData } from "../lib/utils";
|
||||
|
||||
export const columns: ColumnDef<Site>[] = [
|
||||
{
|
||||
@@ -45,4 +71,81 @@ export const columns: ColumnDef<Site>[] = [
|
||||
return date.format("YYYY-MM-DD");
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "action",
|
||||
header: "관리",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<ActionCell data={row.original} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const ActionCell = memo(({ data }: { data: Site }) => {
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { data: menuTreeData, refetch } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, data.siteId],
|
||||
queryFn: () => TreeMenuApi.getTreeMenuList(data.siteId),
|
||||
select: (data) => generateMenuTreeData(data),
|
||||
enabled: !!data.siteId && open,
|
||||
});
|
||||
|
||||
const { mutate: publishSiteMap } = useMutationWrapper({
|
||||
mutationFn: (data: SiteMapFormData["siteMap"]) =>
|
||||
MenuApi.publishSiteMap(data),
|
||||
onSuccess: () => {
|
||||
alert("사이트맵이 저장되었습니다.", () => {
|
||||
setOpen(false);
|
||||
refetch();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<SiteMapFormData>({
|
||||
resolver: zodResolver(siteMapFormSchema),
|
||||
});
|
||||
const fieldArray = useFieldArray({
|
||||
control: form.control,
|
||||
name: "siteMap",
|
||||
});
|
||||
|
||||
const handlePublishSiteMap = async () => {
|
||||
if (await confirm("저장하시겠습니까?")) {
|
||||
publishSiteMap(form.getValues("siteMap"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">사이트맵</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data.siteNm} - 사이트맵 관리</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<div className="flex items-center justify-end gap-2 p-2">
|
||||
<span className="text-xl">표시여부</span>
|
||||
<Checkbox checked />
|
||||
</div>
|
||||
<MenuTree tree={menuTreeData ?? []} fieldArray={fieldArray} />
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={handlePublishSiteMap}>저장</Button>
|
||||
<DialogClose asChild>
|
||||
<Button color="white">취소</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
ActionCell.displayName = "ActionCell";
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
|
||||
type EditableNodeProps = {
|
||||
depth?: number;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
} & ComponentProps<typeof Input>;
|
||||
|
||||
function EditableNode({
|
||||
depth,
|
||||
onSave,
|
||||
onCancel,
|
||||
...props
|
||||
}: EditableNodeProps) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-2 rounded-sm bg-accent p-2"
|
||||
style={{ marginInlineStart: !depth ? 0 : depth * 5 + 5 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Input {...props} className="h-[2.5rem] !text-[1.3rem] text-foreground" />
|
||||
<div className="flex items-center gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 !bg-transparent"
|
||||
onClick={onSave}
|
||||
aria-label="저장"
|
||||
>
|
||||
<i className="diveicon di-check text-2xl" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>저장</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 !bg-transparent"
|
||||
onClick={onCancel}
|
||||
aria-label="취소"
|
||||
>
|
||||
<i className="diveicon di-x" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>취소</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { EditableNode };
|
||||
@@ -0,0 +1,128 @@
|
||||
import { useState } from "react";
|
||||
import type { NodeModel } from "@minoru/react-dnd-treeview";
|
||||
import type { UseFieldArrayReturn } from "react-hook-form";
|
||||
import { Button, Checkbox, cn } from "@common/components";
|
||||
import { type TreeMenu } from "@/entities/tree-menu";
|
||||
import type { SiteMapFormData, SiteMapPayloadData } from "@/entities/menu";
|
||||
import { EditableNode } from "./editable-node";
|
||||
|
||||
type MenuNodeProps = {
|
||||
node: NodeModel<TreeMenu>;
|
||||
depth: number;
|
||||
isOpen: boolean;
|
||||
fieldArray: UseFieldArrayReturn<SiteMapFormData>;
|
||||
onToggle: (id: NodeModel["id"]) => void;
|
||||
};
|
||||
|
||||
function MenuNode({
|
||||
node,
|
||||
depth,
|
||||
isOpen,
|
||||
fieldArray,
|
||||
onToggle,
|
||||
}: MenuNodeProps) {
|
||||
const { id, text, data } = node;
|
||||
const indent = depth * 10 + 5;
|
||||
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [nodeName, setNodeName] = useState(text);
|
||||
const [otptYn, setOtptYn] = useState(data?.otptYn ?? "N");
|
||||
|
||||
const updateField = (id: number, newField: SiteMapPayloadData) => {
|
||||
const existingFieldIndex = fieldArray.fields.findIndex(
|
||||
(field) => field.menuCd === id,
|
||||
);
|
||||
if (existingFieldIndex !== -1) {
|
||||
fieldArray.update(existingFieldIndex, newField);
|
||||
} else {
|
||||
fieldArray.append(newField);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggle = () => onToggle(id);
|
||||
|
||||
const handleSave = async () => {
|
||||
const newField = {
|
||||
menuCd: id as number,
|
||||
menuSubKornNm: nodeName,
|
||||
otptYn,
|
||||
};
|
||||
updateField(id as number, newField);
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setEditMode(false);
|
||||
setNodeName(text);
|
||||
};
|
||||
|
||||
const handleOtptYnChange = (checked: boolean) => {
|
||||
const newField = {
|
||||
menuCd: id as number,
|
||||
menuSubKornNm: nodeName,
|
||||
otptYn: checked ? "Y" : "N",
|
||||
};
|
||||
updateField(id as number, newField);
|
||||
setOtptYn(checked ? "Y" : "N");
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ paddingInlineStart: indent }}
|
||||
className={cn(
|
||||
"group flex cursor-pointer items-center gap-2 rounded-sm p-2 hover:bg-accent",
|
||||
editMode && "p-0 pr-2 hover:bg-transparent",
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggle();
|
||||
}}
|
||||
>
|
||||
{editMode ? (
|
||||
<EditableNode
|
||||
value={nodeName}
|
||||
onChange={(e) => setNodeName(e.target.value)}
|
||||
onSave={handleSave}
|
||||
onCancel={handleCancel}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
{data?.children?.length && (
|
||||
<i
|
||||
className={cn(
|
||||
"diveicon di-arrow-forward mr-2 text-base text-label",
|
||||
isOpen && "rotate-90",
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<span className="text-[1.4rem] font-medium text-label">
|
||||
{nodeName}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="invisible !bg-transparent group-hover:visible"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setEditMode(true);
|
||||
}}
|
||||
>
|
||||
<i className="diveicon di-pencil" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Checkbox
|
||||
className="ml-auto"
|
||||
checked={otptYn === "Y"}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onCheckedChange={handleOtptYnChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { MenuNode };
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { useRef } from "react";
|
||||
import type { UseFieldArrayReturn } from "react-hook-form";
|
||||
import {
|
||||
Tree,
|
||||
type NodeModel,
|
||||
type TreeMethods,
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { Button } from "@common/components";
|
||||
import type { SiteMapFormData } from "@/entities/menu";
|
||||
import { type TreeMenu } from "@/entities/tree-menu";
|
||||
import { MenuNode } from "./menu-node";
|
||||
|
||||
type MenuTreeProps = {
|
||||
tree: NodeModel<TreeMenu>[];
|
||||
fieldArray: UseFieldArrayReturn<SiteMapFormData>;
|
||||
};
|
||||
|
||||
function MenuTree({ tree, fieldArray }: MenuTreeProps) {
|
||||
const treeMethods = useRef<TreeMethods>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="max-h-[calc(100vh-33.5rem)] min-h-[40vh] overflow-auto">
|
||||
<Tree
|
||||
tree={tree}
|
||||
rootId={0}
|
||||
ref={treeMethods}
|
||||
render={(node: NodeModel<TreeMenu>, props) => {
|
||||
return <MenuNode node={node} fieldArray={fieldArray} {...props} />;
|
||||
}}
|
||||
onDrop={() => {}}
|
||||
canDrag={() => false}
|
||||
canDrop={() => false}
|
||||
classes={{
|
||||
container: "flex flex-col",
|
||||
dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm",
|
||||
placeholder: "relative",
|
||||
}}
|
||||
sort={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex-1 items-center px-0"
|
||||
onClick={() => {
|
||||
treeMethods.current?.openAll();
|
||||
}}
|
||||
>
|
||||
<i className="diveicon di-arrows-angle-expand" />
|
||||
전체펼침
|
||||
</Button>
|
||||
<Button
|
||||
color="lightGray"
|
||||
className="flex-1 items-center px-0"
|
||||
onClick={() => {
|
||||
treeMethods.current?.closeAll();
|
||||
}}
|
||||
>
|
||||
<i className="diveicon di-arrows-angle-contract" />
|
||||
전체접기
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { MenuTree };
|
||||
@@ -36,9 +36,14 @@ export const columns: ColumnDef<Post>[] = [
|
||||
accessorKey: "pstgPrdSeNm",
|
||||
header: "게시기간",
|
||||
cell: ({ row }) => {
|
||||
const pstgPrdSeCd = row.original.pstgPrdSeCd;
|
||||
const pstgPrdSeNm = row.original.pstgPrdSeNm;
|
||||
const pstgBgngDt = row.original.pstgBgngDt;
|
||||
const pstgEndDt = row.original.pstgEndDt;
|
||||
|
||||
if (pstgPrdSeCd === "99") {
|
||||
return pstgPrdSeNm;
|
||||
}
|
||||
return `${pstgPrdSeNm} (${dayjs(pstgBgngDt).format("YYYY-MM-DD")} ~ ${dayjs(pstgEndDt).format("YYYY-MM-DD")})`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import type { UserListResponse } from "@/entities/user";
|
||||
import {
|
||||
USER_STATE_CODE,
|
||||
type UserListResponse,
|
||||
type UserStateCode,
|
||||
} from "@/entities/user";
|
||||
import { Badge } from "@common/components";
|
||||
|
||||
export const columns: ColumnDef<UserListResponse>[] = [
|
||||
{
|
||||
@@ -42,8 +47,11 @@ export const columns: ColumnDef<UserListResponse>[] = [
|
||||
header: "기관/부서",
|
||||
},
|
||||
{
|
||||
accessorKey: "userStateCodeName",
|
||||
accessorKey: "userStateCode",
|
||||
header: "회원상태",
|
||||
cell: ({ row }) => {
|
||||
return getUserStateBadge(row.original.userStateCode);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "aprvYmd",
|
||||
@@ -63,3 +71,18 @@ export const columns: ColumnDef<UserListResponse>[] = [
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const getUserStateBadge = (userStateCode: UserStateCode) => {
|
||||
switch (userStateCode) {
|
||||
case USER_STATE_CODE.대기:
|
||||
return <Badge color="gray">대기</Badge>;
|
||||
case USER_STATE_CODE.정상:
|
||||
return <Badge color="green">정상</Badge>;
|
||||
case USER_STATE_CODE.정지:
|
||||
return <Badge color="yellow">정지</Badge>;
|
||||
case USER_STATE_CODE.탈퇴:
|
||||
return <Badge color="purple">탈퇴</Badge>;
|
||||
case USER_STATE_CODE.삭제:
|
||||
return <Badge color="red">삭제</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,20 +16,11 @@ function CreatePage() {
|
||||
defaultValues: {
|
||||
userId: "",
|
||||
userName: "",
|
||||
email: "",
|
||||
password: "",
|
||||
pswdHnt: "",
|
||||
pswdAns: "",
|
||||
mphn: "",
|
||||
telno: "",
|
||||
zip: "",
|
||||
addr: "",
|
||||
daddr: "",
|
||||
laeSeCd: "",
|
||||
ogdpSeCd: "",
|
||||
expryYmd: "",
|
||||
instSeCd: "",
|
||||
enoCd: "",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ export const ENDPOINT = {
|
||||
GROUP_USERS: "/cms-service/api/v1/group-users",
|
||||
MANAGER_IP: "/cms-service/api/v1/mgrip",
|
||||
APILOG: "/cms-service/api/v1/api-logs",
|
||||
SITE_AUTHORITY: "/cms-service/api/v1/site-authrt",
|
||||
},
|
||||
BOARD_SERVICE: {
|
||||
BBS: "/board-service/api/v1/bbs",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
NEXT_PUBLIC_SITE_ID = SN
|
||||
NEXT_PUBLIC_PORT = 11302
|
||||
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11302
|
||||
NEXT_PUBLIC_ADDR_URL = http://con.mind-one.co.kr:11082
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함
|
||||
NEXT_PUBLIC_RSONE_DC_URL = https://apigateway.rsonesoft.com #자료관 데이터
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import FeatureClickEventPopup from './featureClickEventPopup';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import { UiModal, UiTable, DatePickerRange } from '@common/business_components/ui';
|
||||
import { UiModal, UiTable, DatePickerRange, SelectBox } from '@common/business_components/ui';
|
||||
import { Button, Input, Label } from '@common/components';
|
||||
import { UiTable2 } from '@common/business_components/ui/table/index-2';
|
||||
import { UITab2 } from '@common/business_components/ui/tab/index-2';
|
||||
@@ -11,8 +10,9 @@ import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox';
|
||||
import { MsSwagePplCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2, MsSwageMhlCol2, ExclnSewagePumpCol2, stofcbrCol2 } from './components/common/table';
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar';
|
||||
//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi';
|
||||
import { useDcTree,flattenFolders, useSggCd, useCityCd, addFileName, getFileDownload } from './components/searchModalReportApi';
|
||||
import { useDcTree, flattenFolders, useSggCd, useCityCd, addFileName, getFileDownload } from './components/searchModalReportApi';
|
||||
import { useCityCdStore, type SelectDataType } from './commonStore';
|
||||
import { MS_SWAGE_PPL_USE_CODES, MS_SWAGE_PPL_CODES, MS_SWAGE_MHL_USE_CODES } from '../map/components/common/table';
|
||||
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
@@ -95,8 +95,10 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const [tableCount, setTableCount] = useState<number>(0);
|
||||
const [curPage, setCurPage] = useState(1);
|
||||
const [basicType, setBasicType] = useState(BASIC_TYPES[0]?.val ?? "");
|
||||
const [ctpvCd, setCtpvCd] = useState('0');
|
||||
const [sigungu, setSigungu] = useState('0');
|
||||
const [시도, set시도] = useState(0);
|
||||
const [시군구, set시군구] = useState(0);
|
||||
const [ctpvCd, setCtpvCd] = useState('0');
|
||||
const [sigungu, setSigungu] = useState('0');
|
||||
//const [swageProcessingZone, set처리구역] = useState("");
|
||||
const [gubun, setGubun] = useState(""); // 하수정비기본계획 report
|
||||
// 날짜 상태 선언
|
||||
@@ -104,8 +106,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const [시작2, set시작2] = useState("");
|
||||
const [종료1, set종료1] = useState("");
|
||||
const [종료2, set종료2] = useState("");
|
||||
const [변수1, set변수1] = useState("");
|
||||
const [변수2, set변수2] = useState("");
|
||||
const [변수1, set변수1] = useState<string>("");
|
||||
const [변수2, set변수2] = useState<string>("");
|
||||
const [변수3, set변수3] = useState("");
|
||||
const [변수4, set변수4] = useState("");
|
||||
// 시작, 종료, 경과년도
|
||||
@@ -121,7 +123,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const [가동개시_종료_일, set가동개시_종료_일] = useState('');
|
||||
const [fromDate, setFromDate] = useState(() => { const date = new Date(); date.setDate(date.getDate() - 7); return date })
|
||||
const [toDate, setToDate] = useState(new Date())
|
||||
const [filteredSiguns, setFilteredSiguns] = useState<SelectDataType[]>([{ val: '0', text: '전체', sido: '0' }]);
|
||||
const [filteredSiguns, setFilteredSiguns] = useState<SelectDataType[]>([{ val: '0', text: '전체', sido: '0' }]);
|
||||
|
||||
// 로딩바
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -129,7 +131,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const sidos = useCityCd();
|
||||
const siguns = useSggCd();
|
||||
const reportOriginData = useDcTree(""); //:Folder[]
|
||||
|
||||
|
||||
// 오늘 날짜 구하기
|
||||
const today = new Date();
|
||||
const todayYear = today.getFullYear().toString();
|
||||
@@ -162,7 +164,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
else if (basicType === 'tb_ms_swage_pmstn') dynamicHeadlist = ExclnSewagePumpCol2; // 하수펌프장
|
||||
else if (basicType === 'tb_ms_stofcbr') dynamicHeadlist = stofcbrCol2; // 우수토실
|
||||
|
||||
|
||||
|
||||
// 조회 버튼 클릭 시 1페이지로 이동하도록 수정
|
||||
const handleSearch = () => {
|
||||
setCurPage(1); // 페이지를 1로 초기화
|
||||
@@ -187,17 +189,17 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleCtpvCdChange = (val: string|number) => { // 하수도정비기본계획.시도
|
||||
const handleCtpvCdChange = (val: string | number) => { // 하수도정비기본계획.시도
|
||||
const sidoOption = sidos.find((n) => n.val === val);
|
||||
setCtpvCd(String(val));
|
||||
if (val !== '0') {
|
||||
const filterSiguns = siguns.filter(item => item.sido === sidoOption?.text);
|
||||
filterSiguns.unshift({ val: '0', text: '전체', sido: '0' });
|
||||
filterSiguns.unshift({ val: '0', text: '전체', sido: '0' });
|
||||
setFilteredSiguns(filterSiguns);
|
||||
}
|
||||
}
|
||||
setSigungu("0");
|
||||
setGubun("");
|
||||
setYearFilter({시작: "",종료: "",});
|
||||
setYearFilter({ 시작: "", 종료: "", });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -215,6 +217,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
set가동개시_종료_월("");
|
||||
set가동개시_시작_일("");
|
||||
set가동개시_종료_일("");
|
||||
set시도(0);
|
||||
set시군구(0);
|
||||
setCtpvCd('0');
|
||||
setSigungu('0');
|
||||
setYearFilter({
|
||||
@@ -237,7 +241,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
}
|
||||
}, [curPage]);
|
||||
|
||||
|
||||
|
||||
// 검색 API 호출 함수
|
||||
const fetchFilteredData = async () => {
|
||||
setIsLoading(true); // 로딩 시작
|
||||
@@ -248,6 +252,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
size: '10',
|
||||
sort: 'string',
|
||||
});
|
||||
console.log('basicType', basicType);
|
||||
|
||||
let cqlFilters: string[] = [];
|
||||
if (activeTab === 'basic') {
|
||||
@@ -272,27 +277,27 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
if (ctpvCd !== "0") { // 시도검색
|
||||
const sidoOption = sidos.find((n) => n.val === ctpvCd);
|
||||
filterReportData = filterReportData.filter(item => item.sido === sidoOption?.text);
|
||||
}
|
||||
}
|
||||
if (sigungu !== "0") { // 시군구검색
|
||||
const sigunguOption = filteredSiguns.find((n) => n.val === sigungu);
|
||||
filterReportData = filterReportData.filter(item => item.sigungu === sigunguOption?.text);
|
||||
}
|
||||
if (yearFilter.시작 != "" && yearFilter.종료 == "") //승인년도검색
|
||||
filterReportData = filterReportData.filter(item => (item.approvalYear)? item.approvalYear >= yearFilter.시작+"년" : null);
|
||||
if (yearFilter.시작 == "" && yearFilter.종료 != "")
|
||||
filterReportData = filterReportData.filter(item => (item.approvalYear)? item.approvalYear <= yearFilter.종료+"년" : null);
|
||||
filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear >= yearFilter.시작 + "년" : null);
|
||||
if (yearFilter.시작 == "" && yearFilter.종료 != "")
|
||||
filterReportData = filterReportData.filter(item => (item.approvalYear) ? item.approvalYear <= yearFilter.종료 + "년" : null);
|
||||
if (yearFilter.시작 != "" && yearFilter.종료 != "") {
|
||||
filterReportData = filterReportData.filter(item => {
|
||||
return (item.approvalYear)? item.approvalYear >= yearFilter.시작+"년" && item.approvalYear <= yearFilter.종료+"년": null;
|
||||
return (item.approvalYear) ? item.approvalYear >= yearFilter.시작 + "년" && item.approvalYear <= yearFilter.종료 + "년" : null;
|
||||
});
|
||||
}
|
||||
if (gubun !== "") { // 구분검색
|
||||
filterReportData = filterReportData.filter(item => (item.plantZoneGugun)? String(item.plantZoneGugun).includes(String(gubun)) : null);
|
||||
filterReportData = filterReportData.filter(item => (item.plantZoneGugun) ? String(item.plantZoneGugun).includes(String(gubun)) : null);
|
||||
}
|
||||
console.log('report count:', filterReportData.length);
|
||||
setTableCount(filterReportData.length || 0);
|
||||
filterReportData = filterReportData.slice((curPage-1)*10, curPage*10); // paging
|
||||
filterReportData = filterReportData?.map((item, index)=>({...item, no:(curPage-1)*10+index+1}));
|
||||
filterReportData = filterReportData.slice((curPage - 1) * 10, curPage * 10); // paging
|
||||
filterReportData = filterReportData?.map((item, index) => ({ ...item, no: (curPage - 1) * 10 + index + 1 }));
|
||||
//const fileList = await getDcFile("CLS20250530000041");
|
||||
addFileName(filterReportData).then(data => {
|
||||
filterReportData = data;
|
||||
@@ -378,6 +383,11 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
cqlFilters.push(`fclt_cpct BETWEEN ${시작1} AND ${종료1}`);
|
||||
else if (시작1) cqlFilters.push(`fclt_cpct >= ${시작1}`);
|
||||
else if (종료1) cqlFilters.push(`fclt_cpct <= ${종료1}`);
|
||||
// 처리구역 면적
|
||||
if (시작2 && 종료2)
|
||||
cqlFilters.push(`prcsZoneAreaSum BETWEEN ${시작2} AND ${종료2}`);
|
||||
else if (시작2) cqlFilters.push(`prcsZoneAreaSum >= ${시작2}`);
|
||||
else if (종료2) cqlFilters.push(`prcsZoneAreaSum <= ${종료2}`);
|
||||
// 하수처리시설명
|
||||
if (변수1) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${변수1}%'`);
|
||||
return cqlFilters;
|
||||
@@ -386,8 +396,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
// 하수처리구역용 CQL 필터 생성
|
||||
const zoneCqlFilters = () => {
|
||||
const cqlFilters: string[] = [];
|
||||
if (ctpvCd) cqlFilters.push(`ctpv_cd='${ctpvCd}'`);
|
||||
if (sigungu) cqlFilters.push(`sgg_cd='${sigungu}'`);
|
||||
if (시도) cqlFilters.push(`ctpv_cd='${시도}'`);
|
||||
if (시군구) cqlFilters.push(`sgg_cd='${시군구}'`);
|
||||
if (변수1) cqlFilters.push(`swtrmt_zone_nm ILIKE '%${변수1}%'`);
|
||||
if (변수2) cqlFilters.push(`swtrmt_fclt_nm ILIKE '%${변수2}%'`);
|
||||
return cqlFilters;
|
||||
@@ -412,8 +422,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
cqlFilters.push(
|
||||
`instl_ymd >= '${startDate}' and instl_ymd <= '${endDate}'`,
|
||||
);
|
||||
// if (변수1) cqlFilters.push(`경과년도='${변수1}'`);
|
||||
if (변수2) cqlFilters.push(`usg_cd='${변수2}'`);
|
||||
if (변수1) cqlFilters.push(`usg_cd='${변수1}'`);
|
||||
// 구경(mm) 범위
|
||||
if (시작1 && 종료1) cqlFilters.push(`swage_mh_diam BETWEEN ${시작1} AND ${종료1}`);
|
||||
else if (시작1) cqlFilters.push(`swage_mh_diam >= ${시작1}`);
|
||||
@@ -447,7 +456,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
cqlFilters.push(`daily_prcs_cpct BETWEEN ${시작1} AND ${종료1}`);
|
||||
else if (시작1) cqlFilters.push(`daily_prcs_cpct >= ${시작1}`);
|
||||
else if (종료1) cqlFilters.push(`daily_prcs_cpct <= ${종료1}`);
|
||||
// 우수양수능력 범위
|
||||
// 양수능력 범위
|
||||
if (시작2 && 종료2)
|
||||
cqlFilters.push(`excln_wpmp_ablt BETWEEN ${시작2} AND ${종료2}`);
|
||||
else if (시작2) cqlFilters.push(`excln_wpmp_ablt >= ${시작2}`);
|
||||
@@ -514,7 +523,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
// 설치년도 범위
|
||||
if (ctpvCd) cqlFilters.push(`ctpv_cd='${ctpvCd}'`);
|
||||
if (sigungu) cqlFilters.push(`sgg_cd='${sigungu}'`);
|
||||
|
||||
|
||||
return cqlFilters;
|
||||
};
|
||||
|
||||
@@ -583,38 +592,32 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
}}
|
||||
/>
|
||||
</div> */}
|
||||
{/* 용도코드 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
용도코드
|
||||
</Label>
|
||||
<Input
|
||||
className="mr-8 w-[100px] !text-xl placeholder:text-center"
|
||||
placeholder="입력"
|
||||
{/* 용도 */}
|
||||
<SelectBox2
|
||||
className="w-60 mr-8 z-[102]"
|
||||
selectClass="rounded-lg bg-white text-xl"
|
||||
label="용도"
|
||||
selectArray={MS_SWAGE_PPL_USE_CODES}
|
||||
value={변수1}
|
||||
onChange={(e) => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, "");
|
||||
set변수1(val);
|
||||
onSelectValue={(val) => {
|
||||
set변수1(String(val));
|
||||
}}
|
||||
/>
|
||||
{/* 재질코드 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
재질코드
|
||||
</Label>
|
||||
<Input
|
||||
className="mr-8 w-[100px] !text-xl placeholder:text-center"
|
||||
placeholder="입력"
|
||||
{/* 재질 */}
|
||||
<SelectBox2
|
||||
className="w-60 mr-8 z-[102]"
|
||||
selectClass="rounded-lg bg-white text-xl"
|
||||
label="재질"
|
||||
selectArray={MS_SWAGE_PPL_CODES}
|
||||
value={변수2}
|
||||
onChange={(e) => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, "");
|
||||
set변수2(val);
|
||||
onSelectValue={(val) => {
|
||||
set변수2(String(val));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center mb-2">
|
||||
{/* 행정읍면동코드 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
{/* <Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
행정읍면동코드
|
||||
</Label>
|
||||
<Input
|
||||
@@ -626,7 +629,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
const val = e.target.value.replace(/[^0-9]/g, "");
|
||||
set변수3(val);
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
{/* 관경 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
관경(mm)
|
||||
@@ -700,7 +703,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
/>
|
||||
{/* 시설용량 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
시설용량
|
||||
시설용량(㎥/d)
|
||||
</Label>
|
||||
<Input
|
||||
className="w-[100px] !text-xl placeholder:text-center"
|
||||
@@ -723,6 +726,33 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
set종료1(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2 flex items-center">
|
||||
{/* 처리구역 면적 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
처리구역 면적(m²)
|
||||
</Label>
|
||||
<Input
|
||||
className="w-[100px] !text-xl placeholder:text-center"
|
||||
placeholder="입력"
|
||||
value={시작2}
|
||||
onChange={(e) => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, "");
|
||||
set시작2(val);
|
||||
}}
|
||||
/>
|
||||
<span className="mx-2 text-xl font-semibold">~</span>
|
||||
<Input
|
||||
className="mr-8 w-[100px] !text-xl placeholder:text-center"
|
||||
placeholder="입력"
|
||||
value={종료2}
|
||||
onChange={(e) => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, "");
|
||||
set종료2(val);
|
||||
}}
|
||||
/>
|
||||
{/* 하수처리시설명 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
하수처리시설명
|
||||
@@ -743,13 +773,13 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
<div className="mb-3">
|
||||
<div className="mb-2 flex items-center">
|
||||
<SidoSigunguSelectBox
|
||||
sido={ctpvCd}
|
||||
sigungu={sigungu}
|
||||
sido={시도}
|
||||
sigungu={시군구}
|
||||
onSidoChange={(sidoId) => {
|
||||
setCtpvCd(sidoId);
|
||||
setSigungu(0);
|
||||
set시도(sidoId);
|
||||
set시군구(0);
|
||||
}}
|
||||
onSigunguChange={setSigungu}
|
||||
onSigunguChange={set시군구}
|
||||
/>
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
하수처리구역명
|
||||
@@ -808,18 +838,15 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
setYearFilter({ ...yearFilter, 경과년도: val });
|
||||
}}
|
||||
/> */}
|
||||
{/* 용도코드 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
용도코드
|
||||
</Label>
|
||||
<Input
|
||||
className="mr-8 w-[100px] !text-xl placeholder:text-center"
|
||||
placeholder="입력"
|
||||
{/* 용도 */}
|
||||
<SelectBox2
|
||||
className="w-60 mr-8 z-[102]"
|
||||
selectClass="rounded-lg bg-white text-xl"
|
||||
label="용도"
|
||||
selectArray={MS_SWAGE_MHL_USE_CODES}
|
||||
value={변수1}
|
||||
onChange={(e) => {
|
||||
// 숫자만 입력 허용
|
||||
const val = e.target.value.replace(/[^0-9]/g, "");
|
||||
set변수1(val);
|
||||
onSelectValue={(val) => {
|
||||
set변수1(String(val));
|
||||
}}
|
||||
/>
|
||||
{/* 하수맨홀 구경 */}
|
||||
@@ -876,7 +903,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
/> */}
|
||||
{/* 일일처리용량 - 오수맨홀 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
일일처리용량
|
||||
일일처리용량(㎥)
|
||||
</Label>
|
||||
<Input
|
||||
className="w-[100px] !text-xl placeholder:text-center"
|
||||
@@ -899,9 +926,9 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
set종료1(val);
|
||||
}}
|
||||
/>
|
||||
{/* 우수양수능력 - 우수맨홀 */}
|
||||
{/* 양수능력 - 우수맨홀 */}
|
||||
<Label className="mr-2 w-auto text-xl text-gray-600">
|
||||
우수양수능력
|
||||
양수능력(m²/d)
|
||||
</Label>
|
||||
<Input
|
||||
className="w-[100px] !text-xl placeholder:text-center"
|
||||
@@ -1058,21 +1085,21 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
{/* 시도, 시군구, 하수처리구역 한 줄 */}
|
||||
<div className="mb-6 flex items-center">
|
||||
<SelectBox2
|
||||
className="w-[150px] mr-8"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="시도"
|
||||
selectArray={sidos}
|
||||
value={ctpvCd}
|
||||
onSelectValue={handleCtpvCdChange}
|
||||
/>
|
||||
<SelectBox2
|
||||
className="w-48"
|
||||
selectClass="w-48 rounded-lg bg-white truncate"
|
||||
label="시군구"
|
||||
selectArray={filteredSiguns}
|
||||
value={sigungu}
|
||||
onSelectValue={val => setSigungu(String(val))}
|
||||
/>
|
||||
className="w-[150px] mr-8"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="시도"
|
||||
selectArray={sidos}
|
||||
value={ctpvCd}
|
||||
onSelectValue={handleCtpvCdChange}
|
||||
/>
|
||||
<SelectBox2
|
||||
className="w-48"
|
||||
selectClass="w-48 rounded-lg bg-white truncate"
|
||||
label="시군구"
|
||||
selectArray={filteredSiguns}
|
||||
value={sigungu}
|
||||
onSelectValue={val => setSigungu(String(val))}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -1095,7 +1122,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
endLabel=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 조회 버튼 중앙 정렬 */}
|
||||
<div className="absolute bottom-4 right-4 mr-2">
|
||||
<Button className="text-base py-2" onClick={handleSearch}>조회</Button>
|
||||
@@ -1133,7 +1160,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
pageSize={tableData.length < 10 ? tableData.length : 10}
|
||||
total={tableCount}
|
||||
children={undefined}
|
||||
cellClick={(activeTab!=='report')?handleCellClick:handleReportCellClick}
|
||||
cellClick={(activeTab !== 'report') ? handleCellClick : handleReportCellClick}
|
||||
onPageChange={setCurPage}
|
||||
resetPageNo={false}
|
||||
cur={curPage}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import { MS_SWAGE_PPL_USE_CODES, MS_SWAGE_PPL_CODES, MS_SWAGE_MHL_CODES, MS_SWAGE_MHL_USE_CODES, DRTW_SEWAGE_PUMP_USE_CODES } from './table';
|
||||
|
||||
interface CodeMapperProps {
|
||||
layerName: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이어별 코드 변환 함수
|
||||
* @param layerName - 레이어 이름
|
||||
* @param data - 변환할 데이터 객체
|
||||
* @returns 변환된 데이터 객체
|
||||
*/
|
||||
export const codeMapper = ({ layerName, data }: CodeMapperProps): any => {
|
||||
if (!data) return data;
|
||||
|
||||
const processedData = { ...data };
|
||||
|
||||
switch (layerName) {
|
||||
case '하수관로':
|
||||
// usg_cd 값 변환 (용도코드)
|
||||
if (processedData.usg_cd) {
|
||||
const usageCodeItem = MS_SWAGE_PPL_USE_CODES.find(
|
||||
code => code.val === processedData.usg_cd.trim()
|
||||
);
|
||||
if (usageCodeItem) {
|
||||
processedData.usg_cd = usageCodeItem.text;
|
||||
}
|
||||
}
|
||||
|
||||
// mqlt_cd 값 변환 (재질코드)
|
||||
if (processedData.mqlt_cd) {
|
||||
const materialCodeItem = MS_SWAGE_PPL_CODES.find(
|
||||
code => code.val === processedData.mqlt_cd.trim()
|
||||
);
|
||||
if (materialCodeItem) {
|
||||
processedData.mqlt_cd = materialCodeItem.text;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '하수맨홀':
|
||||
// usg_cd 값 변환 (용도코드)
|
||||
if (processedData.usg_cd) {
|
||||
const usageCodeItem = MS_SWAGE_MHL_USE_CODES.find(
|
||||
code => code.val === processedData.usg_cd.trim()
|
||||
);
|
||||
if (usageCodeItem) {
|
||||
processedData.usg_cd = usageCodeItem.text;
|
||||
}
|
||||
}
|
||||
|
||||
// mqlt_cd 값 변환 (재질코드)
|
||||
if (processedData.mqlt_cd) {
|
||||
const materialCodeItem = MS_SWAGE_MHL_CODES.find(
|
||||
code => code.val === processedData.mqlt_cd.trim()
|
||||
);
|
||||
if (materialCodeItem) {
|
||||
processedData.mqlt_cd = materialCodeItem.text;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '하수펌프장':
|
||||
// usg_cd 값 변환 (용도코드)
|
||||
if (processedData.usg_cd) {
|
||||
const usageCodeItem = DRTW_SEWAGE_PUMP_USE_CODES.find(
|
||||
code => code.val === processedData.usg_cd.trim()
|
||||
);
|
||||
if (usageCodeItem) {
|
||||
processedData.usg_cd = usageCodeItem.text;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '우수토실':
|
||||
// prrtymngYn 값 변환 (중점여부)
|
||||
if (processedData.prrtymngYn) {
|
||||
if (processedData.prrtymngYn === 'Y') {
|
||||
processedData.prrtymngYn = '중점우수토실';
|
||||
} else if (processedData.prrtymngYn === 'N') {
|
||||
processedData.prrtymngYn = '우수토실';
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '중점우수토실':
|
||||
if (processedData.manage_no) {
|
||||
processedData.mqlt_cd = processedData.manage_no;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// 다른 레이어는 변환하지 않음
|
||||
break;
|
||||
}
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
/**
|
||||
* 저장 시 코드 역변환 함수 (한글 → 코드)
|
||||
* @param layerName - 레이어 이름
|
||||
* @param data - 역변환할 데이터 객체
|
||||
* @returns 역변환된 데이터 객체
|
||||
*/
|
||||
export const reverseCodeMapper = ({ layerName, data }: CodeMapperProps): any => {
|
||||
if (!data) return data;
|
||||
|
||||
const processedData = { ...data };
|
||||
|
||||
switch (layerName) {
|
||||
case '하수맨홀':
|
||||
// mqlt_cd 값 역변환 (텍스트 → 코드)
|
||||
if (processedData.mqlt_cd && typeof processedData.mqlt_cd === 'string') {
|
||||
const materialCodeItem = MS_SWAGE_MHL_CODES.find(
|
||||
code => code.text === processedData.mqlt_cd.trim()
|
||||
);
|
||||
if (materialCodeItem) {
|
||||
processedData.mqlt_cd = materialCodeItem.val;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '우수토실':
|
||||
// prrtymngYn 값 역변환 (텍스트 → Y/N)
|
||||
if (processedData.prrtymngYn) {
|
||||
if (processedData.prrtymngYn === '중점우수토실') {
|
||||
processedData.prrtymngYn = 'Y';
|
||||
} else if (processedData.prrtymngYn === '우수토실') {
|
||||
processedData.prrtymngYn = 'N';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '하수펌프장':
|
||||
// usg_cd 값 역변환 (텍스트 → 코드)
|
||||
if (processedData.usg_cd && typeof processedData.usg_cd === 'string') {
|
||||
const usageCodeItem = DRTW_SEWAGE_PUMP_USE_CODES.find(
|
||||
code => code.text === processedData.usg_cd.trim()
|
||||
);
|
||||
if (usageCodeItem) {
|
||||
processedData.usg_cd = usageCodeItem.val;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return processedData;
|
||||
};
|
||||
|
||||
// 기본 내보내기
|
||||
export default { codeMapper, reverseCodeMapper };
|
||||
@@ -6,8 +6,8 @@ export const MsSwagePplCol = [
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
{ id: 'pdmt', title: '관경(mm)' },
|
||||
{ id: 'prlg', title: '연장(m)' },
|
||||
{ id: 'usg_cd', title: '용도코드' },
|
||||
{ id: 'mqlt_cd', title: '재질코드' },
|
||||
{ id: 'usg_cd', title: '용도' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'avg_grdn', title: '평균구배(m/m)' },
|
||||
{ id: 'drtw_mltv_area', title: '오수배수면적(m²)' },
|
||||
{ id: 'excln_mltv_area', title: '우수배수면적(m²)' },
|
||||
@@ -63,7 +63,6 @@ export const MS_SWAGE_PPL_USE_CODES = [
|
||||
{ val: "SBA003", text: "우수관" },
|
||||
{ val: "SBA004", text: "오수관" },
|
||||
{ val: "SBA999", text: "기타" },
|
||||
{ val: "그 외", text: "미분류" },
|
||||
];
|
||||
// 하수관로 재질 코드표
|
||||
export const MS_SWAGE_PPL_CODES = [
|
||||
@@ -260,7 +259,6 @@ export const MS_SWAGE_PPL_CODES = [
|
||||
{ val: "MOP948", text: "PE다중벽관" },
|
||||
{ val: "MOP977", text: "전주용입상관" },
|
||||
{ val: "MOP978", text: "MD관" },
|
||||
{ val: "그 외", text: "미분류" },
|
||||
];
|
||||
|
||||
// 하수처리시설
|
||||
@@ -378,7 +376,7 @@ export const MsSwageMhlCol = [
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
// { id: 'mhl_knd_cd', title: '맨홀종류코드' },
|
||||
{ id: 'swage_mh_diam', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'mqlt_cd', title: '재질코드' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'wdth_len', title: '가로길이(m)' },
|
||||
{ id: 'vrtc_len', title: '세로길이(m)' },
|
||||
// { id: 'ang_dir', title: '표고' },
|
||||
@@ -563,13 +561,12 @@ export const DRTW_SEWAGE_PUMP_USE_CODES = [
|
||||
{ val: "SBE900", text: "오수배제" },
|
||||
{ val: "SBE901", text: "우수배제" },
|
||||
{ val: "SBE999", text: "기타" },
|
||||
{ val: "그 외", text: "미분류" },
|
||||
];
|
||||
|
||||
// 우수토실
|
||||
export const stofcbrCol = [
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
{ id: 'pbadms_emd_cd', title: '시설관리번호' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
|
||||
@@ -60,12 +60,11 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic
|
||||
return (
|
||||
<>
|
||||
<VerticalTable
|
||||
headName='자체 입력정보'
|
||||
headName='입력정보'
|
||||
tableData={featureData ? [formData] : []}
|
||||
headlist={inputColumn}
|
||||
pageSize={20}
|
||||
total={0}
|
||||
children={undefined}
|
||||
cellClick={undefined}
|
||||
isEditable={true}
|
||||
onPageChange={function (page: number): void {
|
||||
|
||||
@@ -184,7 +184,7 @@ const Ssc = ({ featureData, isMainSsc, rainMeasureData, onSaveClick }: {
|
||||
<div>
|
||||
{!isMainSsc && (
|
||||
<VerticalTable
|
||||
headName='자체 입력정보'
|
||||
headName='입력정보'
|
||||
tableData={featureData ? [formData] : []}
|
||||
headlist={inputColumn}
|
||||
pageSize={0}
|
||||
|
||||
@@ -107,6 +107,9 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => {
|
||||
};
|
||||
|
||||
const closePopup = () => { // 팝업 닫기
|
||||
setPopupOpen(false);
|
||||
}
|
||||
const infilRsltChart = () => { // 그래프 & 팝업 닫기
|
||||
|
||||
fn.selectInftwtAnlsRslt(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수분석 결과 조회
|
||||
const rsltData = fn.getChartDataInftwtAnlsRslt(data ?? []);
|
||||
@@ -250,6 +253,9 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => {
|
||||
</div>
|
||||
<div className="flex justify-end mt-4 space-x-2">
|
||||
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}>
|
||||
닫기
|
||||
</button>
|
||||
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={infilRsltChart}>
|
||||
분석결과 도표
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -96,6 +96,9 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => {
|
||||
}, [rainInfwtAnlsSn]);
|
||||
|
||||
const closePopup = () => { // 팝업 닫기
|
||||
setPopupOpen(false);
|
||||
}
|
||||
const rdiiRsltChart = () => { // 그래프 & 팝업 닫기
|
||||
fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, pplMngNo).then((data) => {// 강우유입수분석 결과 조회
|
||||
const rsltData1 = fn.getChartDataRdiiAnlsRslt(data ?? []);
|
||||
//const rsltData2 = fn.getChartDataRdiiAnlsRsltMinq(data ?? []);
|
||||
@@ -236,6 +239,9 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => {
|
||||
</div>
|
||||
<div className="flex justify-end mt-4 space-x-2">
|
||||
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={closePopup}>
|
||||
닫기
|
||||
</button>
|
||||
<button className="px-4 py-2 bg-gray-600 text-white rounded" onClick={rdiiRsltChart}>
|
||||
분석결과 도표
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@ const sewagePump = ({ featureData, isDrtwSewagePump, onSaveClick }: { featureDat
|
||||
|
||||
return (
|
||||
<VerticalTable
|
||||
headName='자체 입력정보'
|
||||
headName='입력정보'
|
||||
tableData={featureData ? [formData] : []}
|
||||
headlist={isDrtwSewagePump ? drtwInputColumn : exclnInputColumn}
|
||||
pageSize={0}
|
||||
|
||||
@@ -32,7 +32,7 @@ const Smh = ({ featureData, onSaveClick }: { featureData: any, onSaveClick?: ()
|
||||
|
||||
return (
|
||||
<VerticalTable
|
||||
headName='자체 입력정보'
|
||||
headName='입력정보'
|
||||
tableData={featureData ? [formData] : []}
|
||||
headlist={inputColumn}
|
||||
pageSize={20}
|
||||
|
||||
@@ -10,7 +10,7 @@ const StormWaterPump = ({ featureData }: { featureData: Record<string, any> }) =
|
||||
const filteredData = featureData && Object.entries(featureData).filter(([key]) => validKey.includes(key)).reduce((obj: Record<string, any>, [key, value]) => { obj[key] = value; return obj; }, {});
|
||||
return (
|
||||
<VerticalTable
|
||||
headName='자체 입력정보'
|
||||
headName='입력정보'
|
||||
tableData={[filteredData]}
|
||||
headlist={inputColumn}
|
||||
pageSize={0}
|
||||
|
||||
@@ -11,18 +11,20 @@ import InfilChart from './components/popupComponent/chart/infilChart';
|
||||
import RdiiChart from './components/popupComponent/chart/rdiiChart';
|
||||
import { useInfilChartModalStore, useRdiiChartModalStore } from "./components/popupComponent/chartModalStore";
|
||||
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
|
||||
import { codeMapper } from "./components/common/codeMapper";
|
||||
|
||||
|
||||
// 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 [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
|
||||
const [layerSpecificNm, setLayerSpecificNm] = useState<string>(""); // 레이어 이름 상태
|
||||
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
|
||||
const [rainMeasureData, setRainMeasureData] = useState<any[]>([]); // 강우/측정 측정 정보
|
||||
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 [fetchData, setFetchData] = useState<any>(); // api 호출여부
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
|
||||
const [isMainSsc, setIsMainSsc] = useState(false); // 중점우수토실 여부
|
||||
@@ -133,10 +135,48 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
// 🎯 기본 데이터 포맷팅
|
||||
const formattedData = formatDisplayData(data);
|
||||
|
||||
setfetchData(formattedData);
|
||||
if (featureData.values_.fclt_mng_no) setLayerSpecificNm(featureData.values_.fclt_mng_no);
|
||||
else if (featureData.values_.swtrmt_fclt_cd) setLayerSpecificNm(featureData.values_.swtrmt_fclt_cd);
|
||||
else setLayerSpecificNm(featureData.values_.manage_no);
|
||||
setFetchData(formattedData);
|
||||
setTableData(formattedData);
|
||||
|
||||
switch (selectedLayerName) {
|
||||
case '하수관로':
|
||||
setHeadlist(MsSwagePplCol)
|
||||
setButtonChk(true)
|
||||
|
||||
// ✅ codeMapper 사용하여 API 결과도 변환
|
||||
setTableData(codeMapper({
|
||||
layerName: selectedLayerName,
|
||||
data: { ...formattedData }
|
||||
}));
|
||||
// setFetchData(mappedData);
|
||||
// setTableData(mappedData);
|
||||
break;
|
||||
case '하수맨홀':
|
||||
setHeadlist(MsSwageMhlCol)
|
||||
setButtonChk(true)
|
||||
|
||||
// ✅ codeMapper 사용하여 API 결과도 변환
|
||||
setTableData(codeMapper({
|
||||
layerName: selectedLayerName,
|
||||
data: { ...formattedData }
|
||||
}));
|
||||
// setFetchData(mappedData);
|
||||
// setTableData(mappedData);
|
||||
break;
|
||||
case '하수펌프장':
|
||||
setHeadlist(ExclnSewagePumpCol)
|
||||
setButtonChk(true)
|
||||
|
||||
setTableData(codeMapper({
|
||||
layerName: selectedLayerName,
|
||||
data: { ...formattedData }
|
||||
}));
|
||||
// setFetchData(mappedData);
|
||||
// setTableData(mappedData);
|
||||
break;
|
||||
case '하수처리시설':
|
||||
setHeadlist(SwtrmtFcltCol);
|
||||
setButtonChk(false)
|
||||
@@ -145,6 +185,11 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
setHeadlist(SwtrmtPrcsZoneCol)
|
||||
setButtonChk(false)
|
||||
break;
|
||||
case '우수토실':
|
||||
setHeadlist(stofcbrCol)
|
||||
setButtonChk(true)
|
||||
setIsMainSsc(false)
|
||||
break;
|
||||
case '중점우수토실':
|
||||
setHeadlist(untrtSwgRainCol1)
|
||||
setHeadlist2(untrtSwgRainCol2)
|
||||
@@ -153,51 +198,33 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
// 🎯 processedData도 포맷팅하여 설정
|
||||
const processedFormattedData = formatDisplayData(processedData);
|
||||
|
||||
setfetchData([processedFormattedData])
|
||||
setFetchData([processedFormattedData])
|
||||
setTableData(processedFormattedData);
|
||||
// setIsLoading(false); // 기본 렌더링 완료
|
||||
|
||||
setTableData(codeMapper({
|
||||
layerName: selectedLayerName,
|
||||
data: { ...processedFormattedData }
|
||||
}));
|
||||
|
||||
// 강우/측정 정보 데이터 호출
|
||||
if (data?.chmbr_xcnts && data?.chmbr_ydnts) {
|
||||
fetchRainMeasureData(data.chmbr_xcnts, data.chmbr_ydnts)
|
||||
if (tableData?.chmbr_xcnts && tableData?.chmbr_ydnts) {
|
||||
fetchRainMeasureData(tableData.chmbr_xcnts, tableData.chmbr_ydnts)
|
||||
.then(response => {
|
||||
console.log('additionalResponse', response);
|
||||
setRainMeasureData(response || []);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('추가 데이터 로딩 실패:', error);
|
||||
setRainMeasureData(false);
|
||||
setRainMeasureData([]);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case '우수토실':
|
||||
setHeadlist(stofcbrCol)
|
||||
setButtonChk(true)
|
||||
setIsMainSsc(false)
|
||||
break;
|
||||
case '하수관로':
|
||||
setHeadlist(MsSwagePplCol)
|
||||
setButtonChk(true)
|
||||
break;
|
||||
// case '오수펌프':
|
||||
// setButtonChk(true)
|
||||
// break;
|
||||
// case '빗물펌프':
|
||||
// setButtonChk(true)
|
||||
// break;
|
||||
case '하수펌프장':
|
||||
setHeadlist(ExclnSewagePumpCol)
|
||||
setButtonChk(true)
|
||||
break;
|
||||
case '하수맨홀':
|
||||
setHeadlist(MsSwageMhlCol)
|
||||
setButtonChk(true)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// API 엔드포인트 선택
|
||||
// 자체입력정보 API 엔드포인트 선택
|
||||
let apiEndpoint = '';
|
||||
const getFeatureId = featureId.toString().split('.').pop() || null;
|
||||
if (selectedLayerName === '하수관로') apiEndpoint = `v1/fcltppl/${getFeatureId}`;
|
||||
@@ -214,35 +241,13 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
if (result) {
|
||||
console.log('API 데이터 로드 성공:', result);
|
||||
|
||||
// 하수맨홀인 경우 용도코드를 텍스트로 변환
|
||||
if (selectedLayerName === '하수맨홀') {
|
||||
const processedData = { ...result };
|
||||
// ✅ codeMapper 사용하여 API 결과도 변환
|
||||
const processedApiData = codeMapper({
|
||||
layerName: selectedLayerName,
|
||||
data: { ...result, ...tableData }
|
||||
});
|
||||
|
||||
// mqlt_cd 값 변환 (재질코드)
|
||||
if (processedData.mqlt_cd) {
|
||||
const materialCodeItem = MS_SWAGE_MHL_CODES.find(
|
||||
code => code.val === processedData.mqlt_cd.trim()
|
||||
);
|
||||
if (materialCodeItem) {
|
||||
processedData.mqlt_cd = `${materialCodeItem.text}`;
|
||||
}
|
||||
}
|
||||
|
||||
setfetchData(processedData);
|
||||
} // 🎯 우수토실인 경우 prrtymngYn 값 변환
|
||||
else if (selectedLayerName === '우수토실' && data) {
|
||||
const processedData = { ...result, ...data };
|
||||
|
||||
// prrtymngYn 값 변환
|
||||
if (processedData.prrtymngYn === 'Y') processedData.prrtymngYn = '중점우수토실';
|
||||
else if (processedData.prrtymngYn === 'N') processedData.prrtymngYn = '우수토실';
|
||||
|
||||
console.log('processedData', processedData);
|
||||
|
||||
setfetchData(processedData);
|
||||
} else {
|
||||
setfetchData(result);
|
||||
}
|
||||
setFetchData(processedApiData);
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error('API 데이터 로딩 중 오류:', apiError);
|
||||
@@ -256,7 +261,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
fetchData();
|
||||
}, [featureId, processedData]);
|
||||
|
||||
// 🎯 추가 데이터 API 호출 함수 (독립적으로 실행)
|
||||
// 🎯 추가 데이터 API 호출 함수
|
||||
const fetchRainMeasureData = useCallback(async (chmbrXcnts: string, chmbrYdnts: string) => {
|
||||
if (!chmbrXcnts || !chmbrYdnts) {
|
||||
console.warn('추가 데이터 호출 실패: 필수 매개변수 누락', { chmbrXcnts, chmbrYdnts });
|
||||
@@ -320,7 +325,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('추가 데이터 최종 로딩 실패:', error);
|
||||
setRainMeasureData(false);
|
||||
setRainMeasureData([]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -394,7 +399,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
|
||||
if (result) {
|
||||
alert('저장되었습니다');
|
||||
setfetchData(result.data || result);
|
||||
setFetchData(result.data || result);
|
||||
} else {
|
||||
alert('저장에 실패했습니다');
|
||||
console.error('API 응답 오류:', result);
|
||||
@@ -413,8 +418,13 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
<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-[50]">
|
||||
<div className="card-header bg-primary flex items-center justify-between p-4">
|
||||
<div className="flex items-center">
|
||||
<h3 className="text-2xl font-medium text-white text-label whitespace-nowrap">{layerNm} 정보</h3>
|
||||
{/* <div className="font-medium text-white text-label">(00구 00리 하수관로)</div> */}
|
||||
<h3 className="text-2xl font-medium text-white text-label whitespace-nowrap mr-4">{layerNm}
|
||||
{tableData?.usg_cd && (
|
||||
<>
|
||||
({tableData.usg_cd})
|
||||
</>
|
||||
)} 정보 <br />관리번호 : {layerSpecificNm}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3">
|
||||
@@ -437,7 +447,7 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
)}
|
||||
<Button
|
||||
onClick={onClose}
|
||||
className="text-white hover:text-gray-300 focus:outline-none ml-2"
|
||||
className="text-white hover:text-gray-300 focus:outline-none"
|
||||
>
|
||||
✕
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
'use client'
|
||||
|
||||
// FileUpload.js
|
||||
import React, { useState } from 'react';
|
||||
import axios from 'axios';
|
||||
const SEWERAGE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
|
||||
function FileUpload() {
|
||||
const [file, setFile] = useState(null);
|
||||
const [text, setText] = useState('');
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
setFile(e.target.files[0]);
|
||||
};
|
||||
|
||||
const handleTextChange = (e) => {
|
||||
setText(e.target.value);
|
||||
};
|
||||
|
||||
function getCookie(name:string) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
||||
if (match && match[2]) {
|
||||
return decodeURIComponent(match[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const handleFindUser = (e) => {
|
||||
const auth = getCookie("Authorization");
|
||||
if (auth) console.log("쿠키 auth : " + auth);
|
||||
fetch(SEWERAGE_URL+"/v1/inftwtAnls/users/65a00f65-8460-49af-98ec-042977e56f4b", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({authToken: auth})
|
||||
}).then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
};
|
||||
const handleFindUserOna = (e) => {
|
||||
const auth = getCookie("Authorization");
|
||||
if (auth) console.log("쿠키 auth : " + auth);
|
||||
fetch("/server/user-service/api/v1/users/65a00f65-8460-49af-98ec-042977e56f4b").then((data) => {
|
||||
console.log(data);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!file) {
|
||||
alert('파일을 선택하세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonData = {
|
||||
aplcnYm: '202507',
|
||||
enoCd: 'ABC'
|
||||
};
|
||||
|
||||
// JSON → Blob 으로 변환 + MIME 지정
|
||||
const jsonBlob = new Blob([JSON.stringify(jsonData)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('data', jsonBlob);
|
||||
formData.append('inpFile', file);
|
||||
|
||||
try {
|
||||
const response = await axios.post('http://localhost:8883/sewerage-service/api/v1/rdiiAnls/savewithfile', formData);
|
||||
|
||||
alert('업로드 성공: ' + response.data.message);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('업로드 실패');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="text-2xl">
|
||||
<div>
|
||||
<label>텍스트 입력:</label>
|
||||
<input type="text" value={text} onChange={handleTextChange} />
|
||||
</div>
|
||||
<div>
|
||||
<label>파일 선택:</label>
|
||||
<input type="file" onChange={handleFileChange} />
|
||||
</div>
|
||||
<button type="submit" className="px-4 py-2 bg-gray-500 text-white rounded">업로드</button>
|
||||
<button type="button" className="px-4 py-2 bg-gray-400 text-white rounded"
|
||||
onClick={handleFindUser}>사용자조회</button>
|
||||
<button type="button" className="px-4 py-2 bg-gray-200 text-white rounded"
|
||||
onClick={handleFindUserOna}>사용자조회ona</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default FileUpload;
|
||||
@@ -13,6 +13,12 @@ import { excelToFlowRateData, excelToMatchingResultData, excelToTimePatternData
|
||||
import BaseLayout from '../BaseLayout'
|
||||
import { validateMultipleFiles, FILE_EXTENSIONS, type FileValidationConfig } from '../../fileValidation';
|
||||
import { validateBasicSelectionsWithAlert } from '../../selectionValidation';
|
||||
import { tokenManager } from '@/entities/auth/lib/token-manager';
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@common/components/config";
|
||||
import { getUserInfo } from "@common/components/api";
|
||||
|
||||
|
||||
const formSchema = z.object({
|
||||
managenm: z.string(),
|
||||
@@ -41,6 +47,22 @@ interface ApiSelectOption {
|
||||
}
|
||||
|
||||
const IrSetting = () => {
|
||||
const [input1, setInput1] = useState('');
|
||||
const [input2, setInput2] = useState('');
|
||||
const [select1, setSelect1] = useState('0'); // 환경청코드
|
||||
const [select2, setSelect2] = useState('0'); // 시도코드
|
||||
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경
|
||||
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장코드
|
||||
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역코드
|
||||
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
|
||||
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
|
||||
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
queryFn: () => getUserInfo(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@@ -68,15 +90,6 @@ const IrSetting = () => {
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
return `${year}${month}`;
|
||||
});
|
||||
const [input1, setInput1] = useState('');
|
||||
const [input2, setInput2] = useState('');
|
||||
const [select1, setSelect1] = useState('0'); // 환경청코드
|
||||
const [select2, setSelect2] = useState('0'); // 시도코드
|
||||
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경
|
||||
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장코드
|
||||
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역코드
|
||||
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
|
||||
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
|
||||
|
||||
// 폼 초기화 함수
|
||||
const resetAllForms = () => {
|
||||
@@ -298,6 +311,8 @@ const IrSetting = () => {
|
||||
aplcnYm: selectedDate, // 적용연월
|
||||
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
|
||||
anlsInfo: input2, // 분석정보
|
||||
rgtr: tokenManager.getToken(AUTH_USER_ID) || null, // 등록자 (사용자 ID)
|
||||
rgtrNm: userInfo?.userName, // 등록자 (사용자 ID)
|
||||
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
|
||||
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
|
||||
atflInpNm: file1?.file?.name, // 오수관망도 파일명
|
||||
|
||||
@@ -15,6 +15,11 @@ import { excelToMatchingResultData, excelToRainData } from '@/components/lib/uti
|
||||
import { EditableFormTable, type EditableTableHeadItem } from '../../../components/ui/EditableFormTable'
|
||||
import { validateMultipleFiles, FILE_EXTENSIONS, type FileValidationConfig } from '../../fileValidation';
|
||||
import { validateBasicSelectionsWithAlert } from '../../selectionValidation';
|
||||
import { tokenManager } from '@/entities/auth/lib/token-manager';
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@common/components/config";
|
||||
import { getUserInfo } from "@common/components/api";
|
||||
|
||||
|
||||
// zod 스키마 정의
|
||||
@@ -115,6 +120,12 @@ export default function RTKForm() {
|
||||
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
|
||||
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역 (swtrmtZoneCd)
|
||||
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
queryFn: () => getUserInfo(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
// form 인스턴스 생성
|
||||
const form = useForm<FormSchemaType>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -328,6 +339,8 @@ export default function RTKForm() {
|
||||
|
||||
drtwSwtcRt: parseFloat(formData.rate || "0"), // 오수전환율
|
||||
anlsInfo: formData.analysisInfo, // 분석정보
|
||||
rgtr: tokenManager.getToken(AUTH_USER_ID) || null, // 등록자 (사용자 ID)
|
||||
rgtrNm: userInfo?.userName, // 등록자 (사용자 ID)
|
||||
|
||||
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
|
||||
tbSnRainData: rainData, // 강우자료
|
||||
|
||||
@@ -92,6 +92,8 @@ const Result = () => {
|
||||
|
||||
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
|
||||
setIsLoadingResult(true);
|
||||
setResetSelection(true);
|
||||
|
||||
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
|
||||
console.log("formValues", formValues);
|
||||
|
||||
@@ -156,8 +158,10 @@ const Result = () => {
|
||||
};
|
||||
});
|
||||
|
||||
// setTableData(data.content || []);
|
||||
// setTotalData(tableData.length);
|
||||
// 선택된 행 초기화
|
||||
setSelectedRowData(null);
|
||||
setSelectedRowIndex(undefined);
|
||||
|
||||
setTableData(dataWithRowNumber);
|
||||
setTotalData(dataWithRowNumber.length);
|
||||
console.log("response:", data);
|
||||
@@ -170,6 +174,7 @@ const Result = () => {
|
||||
setTableData([]);
|
||||
setTotalData(0);
|
||||
}
|
||||
setResetSelection(false);
|
||||
};
|
||||
|
||||
// 테이블 셀 클릭 핸들러 추가
|
||||
@@ -264,9 +269,6 @@ const Result = () => {
|
||||
};
|
||||
await handleSearchButtonClick(customFormValues);
|
||||
|
||||
// 선택된 행 초기화
|
||||
setSelectedRowData(null);
|
||||
setSelectedRowIndex(undefined);
|
||||
setIsLoadingResult(false);
|
||||
|
||||
alert('삭제가 완료되었습니다.');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { deleteData, getData } from '@/components/lib/api'
|
||||
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
|
||||
import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useState, useEffect } from 'react'
|
||||
@@ -83,12 +83,13 @@ const Result = () => {
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
// setCurPage(1);
|
||||
handleSearchButtonClick();
|
||||
}, [])
|
||||
|
||||
// setCurPage(1);
|
||||
handleSearchButtonClick();
|
||||
}, [])
|
||||
const auth = useCookieAuth();
|
||||
const handleSearchButtonClick = async (customFormValues?: Partial<ResultFormValues>) => {
|
||||
setIsLoadingResult(true);
|
||||
setResetSelection(true);
|
||||
const formValues = customFormValues || form.getValues(); // react-hook-form에서 현재 값 가져오기
|
||||
console.log("formValues", formValues);
|
||||
|
||||
@@ -112,6 +113,7 @@ const Result = () => {
|
||||
"sggCd": formValues.sggCd,
|
||||
"swageTmplntCd": processedSwageTmplntCd,
|
||||
// "swtrmtZoneCd": formValues.swtrmtZoneCd,
|
||||
"auth" : auth,
|
||||
};
|
||||
} else {
|
||||
inftwtAnlsSearchDto = {
|
||||
@@ -122,6 +124,7 @@ const Result = () => {
|
||||
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
|
||||
"swageTmplntCd": processedSwageTmplntCd,
|
||||
// "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd,
|
||||
"auth" : auth,
|
||||
};
|
||||
}
|
||||
console.log("Search DTO:", inftwtAnlsSearchDto);
|
||||
@@ -154,8 +157,11 @@ const Result = () => {
|
||||
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
|
||||
};
|
||||
});
|
||||
// setTableData(data.content || []);
|
||||
// setTotalData(tableData.length);
|
||||
|
||||
// 선택된 행 초기화
|
||||
setSelectedRowData(null);
|
||||
setSelectedRowIndex(undefined);
|
||||
|
||||
setTableData(dataWithRowNumber);
|
||||
setTotalData(dataWithRowNumber.length);
|
||||
console.log("response:", data);
|
||||
@@ -167,7 +173,10 @@ const Result = () => {
|
||||
alert("분석결과 조회 중 오류가 발생했습니다.");
|
||||
setTableData([]);
|
||||
setTotalData(0);
|
||||
setSelectedRowData(null);
|
||||
setSelectedRowIndex(undefined);
|
||||
}
|
||||
setResetSelection(false);
|
||||
};
|
||||
|
||||
// 테이블 셀 클릭 핸들러 추가
|
||||
@@ -265,16 +274,11 @@ const Result = () => {
|
||||
};
|
||||
await handleSearchButtonClick(customFormValues);
|
||||
|
||||
// 선택된 행 초기화
|
||||
setSelectedRowData(null);
|
||||
setSelectedRowIndex(undefined);
|
||||
setIsLoadingResult(false);
|
||||
|
||||
alert('삭제가 완료되었습니다.');
|
||||
|
||||
setTimeout(() => {
|
||||
setResetSelection(false);
|
||||
}, 100);
|
||||
setResetSelection(false);
|
||||
} catch (error) {
|
||||
setIsLoadingResult(false);
|
||||
console.error("분석결과 파일 삭제 중 오류 발생:", error);
|
||||
@@ -282,7 +286,7 @@ const Result = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
|
||||
@@ -26,10 +26,12 @@ const Page = () => {
|
||||
const [totalData, setTotalData] = useState(0) //총 데이터 수
|
||||
const [isLoading, setIsLoading] = useState(true) //로딩 상태
|
||||
const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
|
||||
|
||||
// 조회
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true)
|
||||
setResetSelection(true);
|
||||
// searchCmmnCd('SITEKNDCD')
|
||||
try {
|
||||
const { data, total } = await searchData('v1/sbmrgPrevnt', {
|
||||
@@ -54,6 +56,7 @@ const Page = () => {
|
||||
setAtflId('');
|
||||
setSbmrgPrevntBizSn('');
|
||||
setIsLoading(false);
|
||||
setResetSelection(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +88,9 @@ const Page = () => {
|
||||
fetchData()
|
||||
}
|
||||
}
|
||||
else {
|
||||
alert('삭제에 실패했습니다.')
|
||||
}
|
||||
}
|
||||
|
||||
// 현황 등록
|
||||
@@ -97,8 +103,8 @@ const Page = () => {
|
||||
atflId: id,
|
||||
sbmrgPrevntBizNm: fileName
|
||||
})
|
||||
if (data) {
|
||||
alert('파일이 등록되었습니다.');
|
||||
if (data) {
|
||||
alert('파일이 등록되었습니다.');
|
||||
setIsModalOpen(false);
|
||||
fetchData();
|
||||
}
|
||||
@@ -183,6 +189,7 @@ const Page = () => {
|
||||
}
|
||||
}}
|
||||
onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}
|
||||
resetSelection={resetSelection}
|
||||
/>
|
||||
</ListDiv>
|
||||
{/* 파일 업로드 모달 */}
|
||||
|
||||
@@ -24,10 +24,12 @@ const Page = () => {
|
||||
const [totalData, setTotalData] = useState(0) //총 데이터 수
|
||||
const [isLoading, setIsLoading] = useState(true) //로딩 상태
|
||||
const [isModalOpen, setIsModalOpen] = useState(false) // 모달 상태
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
|
||||
|
||||
// 데이터 조회
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true)
|
||||
setResetSelection(true);
|
||||
try {
|
||||
const { data, total } = await searchData('v1/dcrpPpl', {
|
||||
fromRegDt: formatDate(fromDate),
|
||||
@@ -51,6 +53,7 @@ const Page = () => {
|
||||
setAtflId('');
|
||||
setDcrpPplMtncBizSn('');
|
||||
setIsLoading(false)
|
||||
setResetSelection(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +185,7 @@ const Page = () => {
|
||||
}
|
||||
}}
|
||||
onPageChange={async (page: number) => { setCurPage(page); await fetchData() }}
|
||||
resetSelection={resetSelection}
|
||||
/>
|
||||
</ListDiv>
|
||||
{/* 파일 업로드 모달 */}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const BASE_URL = 'http://con.mind-one.co.kr:11083/sewerage-service/api';
|
||||
import {useState, useEffect} from 'react';
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
|
||||
export const postData = async (url: string, data: object): Promise<any> => {
|
||||
const options: RequestInit = {
|
||||
@@ -79,4 +80,29 @@ export const deleteData = async (url: string, data?: object): Promise<any> => {
|
||||
const res = await fetch(url.startsWith('/') ? `${url}` : `${BASE_URL}/${url}`, options);
|
||||
if (res.status === 204) return true
|
||||
return res;
|
||||
};
|
||||
|
||||
export function getCookie(name:string) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
||||
if (match && match[2]) {
|
||||
return decodeURIComponent(match[2]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const useCookieAuth = () => {
|
||||
const [data, setData] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetch = async () => {
|
||||
const result = getCookie("Authorization");
|
||||
if (result) {
|
||||
setData(result);
|
||||
}
|
||||
};
|
||||
fetch();
|
||||
console.log("useCookieAuth 호출");
|
||||
}, []);
|
||||
|
||||
return data;
|
||||
};
|
||||
+1
-18
@@ -1,30 +1,13 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const geoServerUrl =
|
||||
process.env.NEXT_PUBLIC_GEOSERVER_URL?.replace(/\/$/, "") ?? "";
|
||||
const cnServerUrl =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL?.replace(/\/$/, "") ?? "";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
// basePath: "/sn",
|
||||
output: "standalone",
|
||||
typescript: {
|
||||
ignoreBuildErrors: true, // 이 줄 추가
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
// async rewrites() {
|
||||
// return [
|
||||
// {
|
||||
// source: '/geoserver/:hasudo*',
|
||||
// destination: `${geoServerUrl}/:hasudo*`,
|
||||
// },
|
||||
// {
|
||||
// source: '/attach-service/:path*',
|
||||
// destination: `${cnServerUrl}/attach-service/:path*`,
|
||||
// },
|
||||
// ];
|
||||
// },
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -12,12 +12,14 @@
|
||||
"@common/assets": "workspace:*",
|
||||
"@common/business_components": "workspace:*",
|
||||
"@common/components": "workspace:*",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@minoru/react-dnd-treeview": "3.5.1-alpha.1",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.67.1",
|
||||
"@tanstack/react-query-devtools": "^5.75.5",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"axios": "^1.8.1",
|
||||
"chart.js": "^4.4.8",
|
||||
@@ -29,12 +31,9 @@
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tus-js-client": "^4.3.1",
|
||||
"zod": "^3.24.2",
|
||||
"zustand": "^5.0.3",
|
||||
"ol": "^7.3.0",
|
||||
"ol-ext": "^4.0.11",
|
||||
"proj4": "^2.9.0",
|
||||
"tus-js-client": "^4.3.1"
|
||||
"zustand": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
@@ -52,6 +51,3 @@
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const subDomain =
|
||||
process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13001";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
async rewrites() {
|
||||
return {
|
||||
beforeFiles: [
|
||||
|
||||
@@ -37,17 +37,22 @@
|
||||
"date-fns": "^3.6.0",
|
||||
"libphonenumber-js": "^1.12.9",
|
||||
"lucide-react": "^0.474.0",
|
||||
"ol": "^7.3.0",
|
||||
"ol-ext": "^4.0.11",
|
||||
"proj4": "^2.9.0",
|
||||
"path": "^0.12.7",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-dnd": "^16.0.1",
|
||||
"recharts": "^2.15.1",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"xlsx": "^0.18.5",
|
||||
"xlsx-populate": "^1.21.0",
|
||||
"zod": "^3.24.2"
|
||||
"zod": "^3.24.2",
|
||||
"@minoru/react-dnd-treeview": "3.5.1-alpha.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
|
||||
Generated
+76
-10
@@ -323,6 +323,9 @@ importers:
|
||||
'@common/components':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common/components
|
||||
'@fontsource-variable/noto-sans-kr':
|
||||
specifier: ^5.2.5
|
||||
version: 5.2.5
|
||||
'@frontend-opensource/use-react-hooks':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
@@ -365,15 +368,6 @@ importers:
|
||||
next:
|
||||
specifier: 15.1.7
|
||||
version: 15.1.7(react-dom@18.3.1)(react@18.3.1)
|
||||
ol:
|
||||
specifier: ^7.3.0
|
||||
version: 7.5.2
|
||||
ol-ext:
|
||||
specifier: ^4.0.11
|
||||
version: 4.0.31(ol@7.5.2)
|
||||
proj4:
|
||||
specifier: ^2.9.0
|
||||
version: 2.15.0
|
||||
react-chartjs-2:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0(chart.js@4.4.8)(react@18.3.1)
|
||||
@@ -459,6 +453,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
i18next:
|
||||
specifier: ^25.3.2
|
||||
version: 25.3.2(typescript@5.6.3)
|
||||
lucide-react:
|
||||
specifier: ^0.474.0
|
||||
version: 0.474.0(react@18.3.1)
|
||||
@@ -474,6 +471,9 @@ importers:
|
||||
react-hot-toast:
|
||||
specifier: ^2.5.2
|
||||
version: 2.5.2(react-dom@18.3.1)(react@18.3.1)
|
||||
react-i18next:
|
||||
specifier: ^15.6.0
|
||||
version: 15.6.0(i18next@25.3.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3)
|
||||
tailwind-merge:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.2
|
||||
@@ -529,6 +529,9 @@ importers:
|
||||
'@hookform/resolvers':
|
||||
specifier: ^4.1.3
|
||||
version: 4.1.3(react-hook-form@7.54.2)
|
||||
'@minoru/react-dnd-treeview':
|
||||
specifier: 3.5.1-alpha.1
|
||||
version: 3.5.1-alpha.1(dnd-core@16.0.1)(react-dnd@16.0.1)(react-dom@18.3.1)(react@18.3.1)
|
||||
'@radix-ui/react-label':
|
||||
specifier: ^2.1.2
|
||||
version: 2.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
|
||||
@@ -559,12 +562,24 @@ importers:
|
||||
lucide-react:
|
||||
specifier: ^0.474.0
|
||||
version: 0.474.0(react@18.3.1)
|
||||
ol:
|
||||
specifier: ^7.3.0
|
||||
version: 7.5.2
|
||||
ol-ext:
|
||||
specifier: ^4.0.11
|
||||
version: 4.0.31(ol@7.5.2)
|
||||
path:
|
||||
specifier: ^0.12.7
|
||||
version: 0.12.7
|
||||
proj4:
|
||||
specifier: ^2.9.0
|
||||
version: 2.15.0
|
||||
react-day-picker:
|
||||
specifier: ^8.10.1
|
||||
version: 8.10.1(date-fns@3.6.0)(react@18.3.1)
|
||||
react-dnd:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1)
|
||||
react-dropzone:
|
||||
specifier: ^14.3.8
|
||||
version: 14.3.8(react@18.3.1)
|
||||
@@ -1092,6 +1107,11 @@ packages:
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.1
|
||||
|
||||
/@babel/runtime@7.27.6:
|
||||
resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: false
|
||||
|
||||
/@babel/template@7.26.9:
|
||||
resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -6914,6 +6934,24 @@ packages:
|
||||
react-is: 16.13.1
|
||||
dev: false
|
||||
|
||||
/html-parse-stringify@3.0.1:
|
||||
resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
|
||||
dependencies:
|
||||
void-elements: 3.1.0
|
||||
dev: false
|
||||
|
||||
/i18next@25.3.2(typescript@5.6.3):
|
||||
resolution: {integrity: sha512-JSnbZDxRVbphc5jiptxr3o2zocy5dEqpVm9qCGdJwRNO+9saUJS0/u4LnM/13C23fUEWxAylPqKU/NpMV/IjqA==}
|
||||
peerDependencies:
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
typescript: 5.6.3
|
||||
dev: false
|
||||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: false
|
||||
@@ -8536,6 +8574,30 @@ packages:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
dev: false
|
||||
|
||||
/react-i18next@15.6.0(i18next@25.3.2)(react-dom@18.3.1)(react@18.3.1)(typescript@5.6.3):
|
||||
resolution: {integrity: sha512-W135dB0rDfiFmbMipC17nOhGdttO5mzH8BivY+2ybsQBbXvxWIwl3cmeH3T9d+YPBSJu/ouyJKFJTtkK7rJofw==}
|
||||
peerDependencies:
|
||||
i18next: '>= 23.2.3'
|
||||
react: ^18.3.1
|
||||
react-dom: '*'
|
||||
react-native: '*'
|
||||
typescript: ^5
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
react-native:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.27.6
|
||||
html-parse-stringify: 3.0.1
|
||||
i18next: 25.3.2(typescript@5.6.3)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
typescript: 5.6.3
|
||||
dev: false
|
||||
|
||||
/react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -9778,7 +9840,6 @@ packages:
|
||||
resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/typescript@5.7.3:
|
||||
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
|
||||
@@ -10066,6 +10127,11 @@ packages:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/void-elements@3.1.0:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/watchpack@2.4.2:
|
||||
resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
Reference in New Issue
Block a user