Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fcc97caf1 | ||
|
|
3a73c8bf1c | ||
|
|
4389a6ddb7 | ||
|
|
9bfd32b5c2 | ||
|
|
f57dcc9788 | ||
|
|
f7849f317c | ||
|
|
7cd1ae2625 | ||
|
|
159fad40ef | ||
|
|
4aa4089ddd | ||
|
|
f6cb75ff1c | ||
|
|
61e4643503 | ||
|
|
6492ad5500 | ||
|
|
8ed0a86cf5 | ||
|
|
52170fc12f | ||
|
|
4c8ecb05e0 | ||
|
|
9e95ad3fad | ||
|
|
523925459c | ||
|
|
a11f760162 | ||
|
|
e35b73d067 | ||
|
|
2620e40872 | ||
|
|
79bc10cdce | ||
|
|
c9f1d05126 | ||
|
|
19bee65195 | ||
|
|
26c01ede4e | ||
|
|
f6cefd6230 | ||
|
|
c6b45499c4 | ||
|
|
30bbf51e77 | ||
|
|
bb6dbfa617 | ||
|
|
1778c07ef9 | ||
|
|
075e3711a9 | ||
|
|
f58f9c3fe3 | ||
|
|
af98524806 | ||
|
|
ecf430dd33 | ||
|
|
892e329207 | ||
|
|
3a191e8d06 | ||
|
|
402b9a5539 | ||
|
|
ef80915519 | ||
|
|
2e83898777 | ||
|
|
3d988b4644 | ||
|
|
c59c17f9f0 | ||
|
|
d5c967e9ad | ||
|
|
55793d8463 | ||
|
|
c4750d9652 | ||
|
|
ba657e8311 | ||
|
|
53fef3bdbf | ||
|
|
21afb058cf | ||
|
|
5232703dd9 | ||
|
|
406ce4056a | ||
|
|
142929510f | ||
|
|
ba6d7f4177 | ||
|
|
56e9218646 | ||
|
|
2d5534b3a6 | ||
|
|
6d967497ba |
@@ -6,8 +6,8 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
InputDatePicker,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -21,7 +21,7 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/apilog/model/table-columns";
|
||||
|
||||
type DateRange = {
|
||||
from: Date | undefined;
|
||||
from?: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ function ApiLogPage() {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [presetDateRange, setPresetDateRange] = useState("");
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
const [userId, setUserId] = useState("");
|
||||
const [apiHttpPmhd, setApiHttpPmhd] = useState("");
|
||||
@@ -84,8 +85,10 @@ function ApiLogPage() {
|
||||
...searchParams,
|
||||
userId,
|
||||
apiHttpPmhd,
|
||||
dmndDtStart: dayjs(dateRange.from).format("YYYY-MM-DD"),
|
||||
dmndDtEnd: dayjs(dateRange.to).format("YYYY-MM-DD"),
|
||||
dmndDtStart: dateRange.from
|
||||
? dayjs(dateRange.from).format("YYYY-MM-DD")
|
||||
: "",
|
||||
dmndDtEnd: dateRange.to ? dayjs(dateRange.to).format("YYYY-MM-DD") : "",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -126,6 +129,7 @@ function ApiLogPage() {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
setPresetDateRange(value);
|
||||
setDateRange(presetDateRange || defaultDateRange);
|
||||
};
|
||||
|
||||
@@ -134,23 +138,31 @@ function ApiLogPage() {
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2">조회기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={(value) => setDateRange(value || defaultDateRange)}
|
||||
>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</DateRangePicker>
|
||||
<Select value={presetDateRange} onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputDatePicker
|
||||
value={dateRange.from}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, from: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
~
|
||||
<InputDatePicker
|
||||
value={dateRange.to}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, to: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
<Label className="ml-4 mr-2 text-label">사용자ID</Label>
|
||||
<Input
|
||||
value={userId}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { Dashboard, DashboardService } from "../model/dashboard-interface";
|
||||
|
||||
export class DashboardApiService implements DashboardService {
|
||||
public async getDashboard(): Promise<Dashboard> {
|
||||
const response = await client.get<Dashboard>(
|
||||
ENDPOINT.CMS_SERVICE.DASHBOARD,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const DashboardApi = new DashboardApiService();
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { ChartData, ChartDataset } from "chart.js";
|
||||
import { getCssVariableValue } from "@common/components";
|
||||
import type {
|
||||
MenuStatisticsContent,
|
||||
SiteStatisticsContent,
|
||||
} from "../model/dashboard-interface";
|
||||
|
||||
export function processSiteAccessData(
|
||||
siteAccessList: SiteStatisticsContent[],
|
||||
): ChartData<"line">["datasets"] {
|
||||
const siteGroups: Record<string, ChartDataset> = siteAccessList.reduce(
|
||||
(acc, item) => {
|
||||
if (!acc[item.siteNm]) {
|
||||
acc[item.siteNm] = {
|
||||
label: item.siteNm,
|
||||
data: Array(24).fill(0), // 24시간 데이터 초기화
|
||||
borderColor: getCssVariableValue(
|
||||
`--chart-${Math.floor(Math.random() * 5) + 1}`,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// 시간대별 접속 카운트 누적
|
||||
const hourIndex = parseInt(item.accessHour, 10);
|
||||
if (!isNaN(hourIndex) && hourIndex >= 0 && hourIndex < 24) {
|
||||
acc[item.siteNm]!.data[hourIndex] = item.accessCnt;
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, ChartDataset>,
|
||||
);
|
||||
|
||||
return Object.values(siteGroups) as ChartData<"line">["datasets"];
|
||||
}
|
||||
|
||||
export function processMenuAccessData(
|
||||
menuAccessList: MenuStatisticsContent[],
|
||||
): ChartData<"bar">["datasets"] {
|
||||
const data = menuAccessList.map((item) => item.accessCnt);
|
||||
|
||||
return [
|
||||
{
|
||||
label: "메뉴별 접속 횟수",
|
||||
data,
|
||||
backgroundColor: "rgb(32, 148, 250)",
|
||||
},
|
||||
] as ChartData<"bar">["datasets"];
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { type ChartData, type ChartOptions } from "chart.js";
|
||||
|
||||
const commonData = {
|
||||
labels: ["1", "2", "3", "4", "5", "6", "7"],
|
||||
datasets: [
|
||||
{
|
||||
fill: true,
|
||||
pointRadius: 0,
|
||||
tension: 0.5,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const commonOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: false,
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const chartData: Record<
|
||||
string,
|
||||
{ data: ChartData<"line" | "bar">; options: ChartOptions<"line" | "bar"> }
|
||||
> = {
|
||||
memberChartData: {
|
||||
data: {
|
||||
...commonData,
|
||||
datasets: [
|
||||
{
|
||||
...commonData.datasets[0],
|
||||
data: [8, 10, 7, 12, 9, 13, 12],
|
||||
borderColor: "rgba(255, 255, 255, 0.6)",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
...commonOptions,
|
||||
scales: {
|
||||
...commonOptions.scales,
|
||||
y: {
|
||||
...commonOptions.scales.y,
|
||||
min: 5,
|
||||
max: 15,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postChartData: {
|
||||
data: {
|
||||
...commonData,
|
||||
datasets: [
|
||||
{
|
||||
...commonData.datasets[0],
|
||||
data: [5200, 5350, 5450, 5520, 5600, 5650, 5678],
|
||||
borderColor: "rgba(86, 138, 255, 1)",
|
||||
backgroundColor: "rgba(86, 138, 255, 0.1)",
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
...commonOptions,
|
||||
},
|
||||
},
|
||||
visitorChartData: {
|
||||
data: {
|
||||
...commonData,
|
||||
datasets: [
|
||||
{
|
||||
...commonData.datasets[0],
|
||||
data: [1800, 2100, 1950, 2300, 2150, 2250, 2345],
|
||||
borderColor: "rgba(149, 131, 252)",
|
||||
backgroundColor: "rgba(149, 131, 252, 0.1)",
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
...commonOptions,
|
||||
},
|
||||
},
|
||||
apiChartData: {
|
||||
data: {
|
||||
...commonData,
|
||||
datasets: [
|
||||
{
|
||||
...commonData.datasets[0],
|
||||
data: [41000, 42500, 43200, 44100, 44800, 45200, 45678],
|
||||
borderColor: "rgba(220, 103, 206)",
|
||||
backgroundColor: "rgba(220, 103, 206, 0.1)",
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
...commonOptions,
|
||||
},
|
||||
},
|
||||
siteAccessChartData: {
|
||||
data: {
|
||||
labels: Array.from({ length: 24 }, (_, i) =>
|
||||
i.toString().padStart(2, "0"),
|
||||
),
|
||||
datasets: [],
|
||||
},
|
||||
options: {
|
||||
...commonOptions,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "top",
|
||||
},
|
||||
tooltip: {
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
text: "시간대",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
text: "접속자 수",
|
||||
},
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
menuAccessChartData: {
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
},
|
||||
options: {
|
||||
...commonOptions,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "top",
|
||||
},
|
||||
tooltip: {
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
title: {
|
||||
text: "메뉴명",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
title: {
|
||||
text: "접속 횟수",
|
||||
},
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
export interface SiteStatisticsContent {
|
||||
siteId: string;
|
||||
siteNm: string;
|
||||
accessHour: string;
|
||||
accessCnt: number;
|
||||
}
|
||||
|
||||
export interface MenuStatisticsContent {
|
||||
menuCd: number;
|
||||
menuNm: string;
|
||||
accessCnt: number;
|
||||
}
|
||||
|
||||
export interface Dashboard {
|
||||
memberTotCnt: number;
|
||||
memberTodayCnt: number;
|
||||
pstTotCnt: number;
|
||||
pstTodayCnt: number;
|
||||
accessTodayCnt: number;
|
||||
accessBefDayPer: number;
|
||||
apiTodayCnt: number;
|
||||
waitMemberCnt: number;
|
||||
waitCntnCnt: number;
|
||||
notiList: [];
|
||||
schdlList: [];
|
||||
siteAccessList: SiteStatisticsContent[];
|
||||
menuAccessList: MenuStatisticsContent[];
|
||||
}
|
||||
|
||||
export interface DashboardService {
|
||||
getDashboard(): Promise<Dashboard>;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Badge } from "@common/components";
|
||||
import type { RecentPost } from "@/entities/post";
|
||||
|
||||
const noticePostTableColumns: ColumnDef<RecentPost>[] = [
|
||||
{
|
||||
header: "번호",
|
||||
cell: ({ row, table }) => {
|
||||
if (row.original.upndFixYn === "Y") {
|
||||
return (
|
||||
<Badge
|
||||
color="orange"
|
||||
className="flex size-8 items-center justify-center"
|
||||
>
|
||||
<i className="diveicon di-volume-up-fill" />
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
const totalRows = table.getRowCount();
|
||||
const currentPage = table.getState().pagination.pageIndex;
|
||||
const pageSize = table.getState().pagination.pageSize;
|
||||
return totalRows - (currentPage * pageSize + row.index);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "pstTtl",
|
||||
header: "제목",
|
||||
},
|
||||
{
|
||||
accessorKey: "wrtrNm",
|
||||
header: "작성자",
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
header: "작성일",
|
||||
cell: ({ row }) => dayjs(row.original.regDt).format("YYYY-MM-DD"),
|
||||
},
|
||||
{
|
||||
accessorKey: "inqNocs",
|
||||
header: "조회수",
|
||||
},
|
||||
];
|
||||
|
||||
const recentPostTableColumns: ColumnDef<RecentPost>[] = [
|
||||
{
|
||||
header: "번호",
|
||||
cell: ({ row, table }) => {
|
||||
const totalRows = table.getRowCount();
|
||||
const currentPage = table.getState().pagination.pageIndex;
|
||||
const pageSize = table.getState().pagination.pageSize;
|
||||
return totalRows - (currentPage * pageSize + row.index);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "pstTtl",
|
||||
header: "제목",
|
||||
},
|
||||
{
|
||||
accessorKey: "wrtrNm",
|
||||
header: "작성자",
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
header: "작성일",
|
||||
cell: ({ row }) => dayjs(row.original.regDt).format("YYYY-MM-DD"),
|
||||
},
|
||||
{
|
||||
accessorKey: "inqNocs",
|
||||
header: "조회수",
|
||||
},
|
||||
];
|
||||
|
||||
export { noticePostTableColumns, recentPostTableColumns };
|
||||
@@ -1,5 +1,409 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel } from "@tanstack/react-table";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
Filler,
|
||||
Legend,
|
||||
Tooltip,
|
||||
type ChartData,
|
||||
} from "chart.js";
|
||||
import { Bar, Line } from "react-chartjs-2";
|
||||
import { PostApi, type RecentPost } from "@/entities/post";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { DashboardApi } from "../api/dashboard-service";
|
||||
import { chartData } from "../model/chart-data";
|
||||
import {
|
||||
noticePostTableColumns,
|
||||
recentPostTableColumns,
|
||||
} from "../model/table-columns";
|
||||
import { processMenuAccessData, processSiteAccessData } from "../lib/utils";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
Filler,
|
||||
Legend,
|
||||
Tooltip,
|
||||
);
|
||||
|
||||
function DashboardPage() {
|
||||
return <></>;
|
||||
const router = useRouter();
|
||||
const { data } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.DASHBOARD],
|
||||
queryFn: () => DashboardApi.getDashboard(),
|
||||
});
|
||||
|
||||
const noticePostQuery = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS],
|
||||
queryFn: () =>
|
||||
PostApi.getRecentPostList({
|
||||
page: 0,
|
||||
size: 5,
|
||||
keywordType: "bbsTypeCd",
|
||||
keyword: "BBSNOTICE",
|
||||
}),
|
||||
});
|
||||
|
||||
const recentPostQuery = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS],
|
||||
queryFn: () =>
|
||||
PostApi.getRecentPostList({
|
||||
page: 0,
|
||||
size: 5,
|
||||
}),
|
||||
});
|
||||
|
||||
const handleNoticePostRowClick = (row: RecentPost) => {
|
||||
router.push(`/bbs/bbsnotice/${row.bbsId}/${row.pstNo}`);
|
||||
};
|
||||
|
||||
const handleRecentPostRowClick = (row: RecentPost) => {
|
||||
router.push(
|
||||
`/bbs/${row.bbsTypeCd.toLowerCase()}/${row.bbsId}/${row.pstNo}`,
|
||||
);
|
||||
};
|
||||
|
||||
const { tableComponent: noticePostTableComponent } = useDataTable({
|
||||
query: noticePostQuery,
|
||||
tableOptions: {
|
||||
columns: noticePostTableColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
variant: "line",
|
||||
onRowClick: handleNoticePostRowClick,
|
||||
},
|
||||
});
|
||||
|
||||
const { tableComponent: recentPostTableComponent } = useDataTable({
|
||||
query: recentPostQuery,
|
||||
tableOptions: {
|
||||
columns: recentPostTableColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
variant: "line",
|
||||
onRowClick: handleRecentPostRowClick,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* 첫번째 라인 */}
|
||||
<div className="grid grid-cols-4 gap-1">
|
||||
{/* 총 회원수 카드 */}
|
||||
<div className="card card-border h-80 !bg-primary !p-0">
|
||||
<div className="relative h-full">
|
||||
<div className="absolute left-0 top-0 p-6 tracking-tighter text-white">
|
||||
<h3 className="text-2xl font-medium">총 회원수</h3>
|
||||
<div className="mt-2 text-6xl font-bold">
|
||||
{data?.memberTotCnt ?? "-"}
|
||||
</div>
|
||||
<p className="mt-1 text-2xl opacity-80">
|
||||
금일 신규 가입자 {data?.memberTodayCnt ?? "-"}명
|
||||
</p>
|
||||
</div>
|
||||
{/* 차트 영역 */}
|
||||
<div className="h-full">
|
||||
<Line
|
||||
data={chartData.memberChartData!.data as ChartData<"line">}
|
||||
options={chartData.memberChartData!.options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 전체 게시물 카드 */}
|
||||
<div className="card card-border h-80 !p-0">
|
||||
<div className="relative h-full">
|
||||
<div className="absolute left-0 top-0 p-6 tracking-tighter">
|
||||
<h3 className="text-2xl font-medium">전체 게시물</h3>
|
||||
<div className="mt-2 text-6xl font-bold">
|
||||
{data?.pstTotCnt ?? "-"}
|
||||
</div>
|
||||
<p className="mt-1 text-2xl opacity-80">
|
||||
금일 +{data?.pstTodayCnt ?? "-"} 등록
|
||||
</p>
|
||||
</div>
|
||||
{/* 차트 영역 */}
|
||||
<div className="h-full">
|
||||
<Line
|
||||
data={chartData.postChartData!.data as ChartData<"line">}
|
||||
options={chartData.postChartData!.options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 오늘 접속자 수 카드 */}
|
||||
<div className="card card-border h-80 !p-0">
|
||||
<div className="relative h-full">
|
||||
<div className="absolute left-0 top-0 p-6 tracking-tighter">
|
||||
<h3 className="text-2xl font-medium">오늘 접속자 수</h3>
|
||||
<div className="mt-2 text-6xl font-bold">
|
||||
{data?.accessTodayCnt ?? "-"}
|
||||
</div>
|
||||
<p className="mt-1 text-2xl opacity-80">
|
||||
전일 대비 +{data?.accessBefDayPer ?? "-"}%
|
||||
</p>
|
||||
</div>
|
||||
{/* 차트 영역 */}
|
||||
<div className="h-full">
|
||||
<Line
|
||||
data={chartData.visitorChartData!.data as ChartData<"line">}
|
||||
options={chartData.visitorChartData!.options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* API 호출 건수 카드 */}
|
||||
<div className="card card-border h-80 !p-0">
|
||||
<div className="relative h-full">
|
||||
<div className="absolute left-0 top-0 p-6 tracking-tighter">
|
||||
<h3 className="text-2xl font-medium">API 호출 건수</h3>
|
||||
<div className="mt-2 text-6xl font-bold">
|
||||
{data?.apiTodayCnt ?? "-"}
|
||||
</div>
|
||||
<p className="mt-1 text-2xl opacity-80">금일 누적</p>
|
||||
</div>
|
||||
{/* 차트 영역 */}
|
||||
<div className="h-full">
|
||||
<Line
|
||||
data={chartData.apiChartData!.data as ChartData<"line">}
|
||||
options={chartData.apiChartData!.options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 두번째 라인 */}
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
{/* 승인 대기 알림 */}
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">승인 대기 알림</h3>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="flex gap-4">
|
||||
{/* 사용자 승인 */}
|
||||
<div className="flex flex-1 flex-col items-start rounded-md bg-accent p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-4 flex h-10 w-10 items-center justify-center rounded-lg bg-red-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-red-500"
|
||||
>
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-2xl font-medium">사용자 승인</span>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="text-5xl font-bold">
|
||||
{data?.waitMemberCnt}건
|
||||
</div>
|
||||
<div className="mt-2 text-2xl text-gray-500">
|
||||
신규 가입 승인 대기
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 매뉴 승인 */}
|
||||
<div className="flex flex-1 flex-col items-start rounded-md bg-accent p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-4 flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-blue-500"
|
||||
>
|
||||
<rect x="3" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="14" width="7" height="7"></rect>
|
||||
<rect x="3" y="14" width="7" height="7"></rect>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-2xl font-medium">메뉴 승인</span>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="text-5xl font-bold">3건</div>
|
||||
<div className="mt-2 text-2xl text-gray-500">
|
||||
매뉴 수정 승인 대기
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 컨텐츠 승인 */}
|
||||
<div className="flex flex-1 flex-col items-start rounded-md bg-accent p-6">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-4 flex h-10 w-10 items-center justify-center rounded-lg bg-yellow-100">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="text-yellow-500"
|
||||
>
|
||||
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
||||
<polyline points="14 2 14 8 20 8"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-2xl font-medium">컨텐츠 승인</span>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<div className="text-5xl font-bold">
|
||||
{data?.waitCntnCnt}건
|
||||
</div>
|
||||
<div className="mt-2 text-2xl text-gray-500">
|
||||
컨텐츠 수정 승인 대기
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
{/* 실시간 알림 */}
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">실시간 알림</h3>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||
<span className="font-medium">신규 사용자 가입</span>
|
||||
<span>1분 전</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||
<span className="font-medium">메뉴 수정 요청</span>
|
||||
<span>5분 전</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||
<span className="font-medium">게시물 신고</span>
|
||||
<span>10분 전</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">컨텐츠 승인 요청</span>
|
||||
<span>15분 전</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 일정 알림 */}
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">일정 알림</h3>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||
<span className="font-medium">3월 정기 시스템 점검</span>
|
||||
<span>내일</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||
<span className="font-medium">서버 업데이트 예정</span>
|
||||
<span>3일 후</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between border-b border-border pb-4">
|
||||
<span className="font-medium">백업 스케줄</span>
|
||||
<span>5일 후</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-medium">정기 보안 점검</span>
|
||||
<span>7일 후</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 세번째 라인 */}
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
{/* 접속자 통계 */}
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">접속자 통계</h3>
|
||||
</div>
|
||||
<div className="h-80 p-4">
|
||||
<Line
|
||||
data={{
|
||||
...chartData.siteAccessChartData!.data,
|
||||
datasets: processSiteAccessData(data?.siteAccessList ?? []),
|
||||
}}
|
||||
options={chartData.siteAccessChartData!.options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* 메뉴별 접속 통계 */}
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium">메뉴별 접속 통계</h3>
|
||||
</div>
|
||||
<div className="h-80 p-4">
|
||||
<Bar
|
||||
data={{
|
||||
labels:
|
||||
data?.menuAccessList?.map((item) => item.menuNm) || [],
|
||||
datasets: processMenuAccessData(data?.menuAccessList || []),
|
||||
}}
|
||||
options={chartData.menuAccessChartData!.options}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 네번째 라인 */}
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
{/* 공지사항 */}
|
||||
<div className="card card-border !p-0">
|
||||
<div className="card-header !m-0 !border-none">
|
||||
<h3 className="text-2xl font-medium">공지사항</h3>
|
||||
</div>
|
||||
{noticePostTableComponent}
|
||||
</div>
|
||||
{/* 신규 게시글 */}
|
||||
<div className="card card-border !p-0">
|
||||
<div className="card-header !m-0 !border-none">
|
||||
<h3 className="text-2xl font-medium">신규 게시글</h3>
|
||||
</div>
|
||||
{recentPostTableComponent}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { DashboardPage };
|
||||
|
||||
@@ -6,8 +6,8 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
InputDatePicker,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -22,7 +22,7 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/loginlog/model/table-columns";
|
||||
|
||||
type DateRange = {
|
||||
from: Date | undefined;
|
||||
from?: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
@@ -32,13 +32,12 @@ function LoginlogPage() {
|
||||
to: dayjs().toDate(),
|
||||
};
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
const [isManualLoading, setIsManualLoading] = useState(false);
|
||||
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [presetDateRange, setPresetDateRange] = useState("");
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
const [userId, setUserId] = useState("");
|
||||
const [errOcrnYn, setErrOcrnYn] = useState("N");
|
||||
const [searchParams, setSearchParams] = useState<LoginlogParams>({
|
||||
@@ -48,6 +47,7 @@ function LoginlogPage() {
|
||||
searchDateTo: dayjs().format("YYYY-MM-DD"),
|
||||
sort: "lgnDt,desc",
|
||||
});
|
||||
const [isManualLoading, setIsManualLoading] = useState(false);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, searchParams, pagination],
|
||||
@@ -88,8 +88,12 @@ function LoginlogPage() {
|
||||
...searchParams,
|
||||
userId,
|
||||
errOcrnYn,
|
||||
searchDateFrom: dayjs(dateRange.from).format("YYYY-MM-DD"),
|
||||
searchDateTo: dayjs(dateRange.to).format("YYYY-MM-DD"),
|
||||
searchDateFrom: dateRange.from
|
||||
? dayjs(dateRange.from).format("YYYY-MM-DD")
|
||||
: "",
|
||||
searchDateTo: dateRange.to
|
||||
? dayjs(dateRange.to).format("YYYY-MM-DD")
|
||||
: "",
|
||||
});
|
||||
query.refetch().finally(() => {
|
||||
setIsManualLoading(false);
|
||||
@@ -133,6 +137,7 @@ function LoginlogPage() {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
setPresetDateRange(value);
|
||||
setDateRange(presetDateRange || defaultDateRange);
|
||||
};
|
||||
|
||||
@@ -141,23 +146,31 @@ function LoginlogPage() {
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2">조회기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={(value) => setDateRange(value || defaultDateRange)}
|
||||
>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</DateRangePicker>
|
||||
<Select value={presetDateRange} onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputDatePicker
|
||||
value={dateRange.from}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, from: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
~
|
||||
<InputDatePicker
|
||||
value={dateRange.to}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, to: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
<Label className="ml-4 mr-2 text-label">사용자ID</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
|
||||
@@ -110,6 +110,7 @@ function MenuPage() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
||||
});
|
||||
form.reset();
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
InputDatePicker,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -24,7 +24,7 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import { columns } from "@/pages/privacylog/model/table-columns";
|
||||
|
||||
type DateRange = {
|
||||
from: Date | undefined;
|
||||
from?: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ function PrivacyLogPage() {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [presetDateRange, setPresetDateRange] = useState("");
|
||||
const [dateRange, setDateRange] = useState<DateRange>(defaultDateRange);
|
||||
const [inqInfoKeyword, setInqInfoKeyword] = useState("");
|
||||
const [searchParams, setSearchParams] = useState<PrivacyLogParams>({
|
||||
@@ -85,8 +86,10 @@ function PrivacyLogPage() {
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
inqInfoKeyword,
|
||||
inqDtStart: dayjs(dateRange.from).format("YYYY-MM-DD"),
|
||||
inqDtEnd: dayjs(dateRange.to).format("YYYY-MM-DD"),
|
||||
inqDtStart: dateRange.from
|
||||
? dayjs(dateRange.from).format("YYYY-MM-DD")
|
||||
: "",
|
||||
inqDtEnd: dateRange.to ? dayjs(dateRange.to).format("YYYY-MM-DD") : "",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -125,6 +128,7 @@ function PrivacyLogPage() {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
setPresetDateRange(value);
|
||||
setDateRange(presetDateRange || defaultDateRange);
|
||||
};
|
||||
|
||||
@@ -133,30 +137,37 @@ function PrivacyLogPage() {
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2">조회기간</Label>
|
||||
<DateRangePicker
|
||||
value={dateRange}
|
||||
onValueChange={(value) => setDateRange(value || defaultDateRange)}
|
||||
>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</DateRangePicker>
|
||||
<Select value={presetDateRange} onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputDatePicker
|
||||
value={dateRange.from}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, from: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
~
|
||||
<InputDatePicker
|
||||
value={dateRange.to}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, to: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
<Label className="ml-4 mr-2 text-label">조회정보</Label>
|
||||
<Input
|
||||
className="h-[3.2rem] w-[20rem] rounded-sm !text-[1.3rem]"
|
||||
value={inqInfoKeyword}
|
||||
onChange={(e) => setInqInfoKeyword(e.target.value)}
|
||||
/>
|
||||
|
||||
<Button className="h-[3.2rem]" type="submit">
|
||||
조회
|
||||
</Button>
|
||||
|
||||
@@ -12,8 +12,8 @@ import {
|
||||
} from "@tanstack/react-table";
|
||||
import {
|
||||
Button,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
InputDatePicker,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -37,7 +37,7 @@ import { columns } from "../model/table-columns";
|
||||
import { MovePostDialog } from "./move-post-dialog";
|
||||
|
||||
type DateRange = {
|
||||
from: Date | undefined;
|
||||
from?: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
@@ -51,22 +51,21 @@ function RecentPage() {
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRange | undefined>({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
});
|
||||
const [tableSort, setTableSort] = useState("regDt,desc");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
const [presetDateRange, setPresetDateRange] = useState("");
|
||||
const [dateRange, setDateRange] = useState<DateRange | undefined>({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
});
|
||||
const [keywordType, setKeywordType] = useState("");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [bbsId, setBbsId] = useState<string | null>(null);
|
||||
const [delYn, setDelYn] = useState("N");
|
||||
|
||||
const [searchParams, setSearchParams] = useState<RecentPostParams>({
|
||||
keywordType: "",
|
||||
keyword: "",
|
||||
@@ -193,6 +192,7 @@ function RecentPage() {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
setPresetDateRange(value);
|
||||
setDateRange(presetDateRange);
|
||||
};
|
||||
|
||||
@@ -250,21 +250,31 @@ function RecentPage() {
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
|
||||
<Label className="mr-2">날짜</Label>
|
||||
<DateRangePicker value={dateRange} onValueChange={setDateRange}>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</DateRangePicker>
|
||||
|
||||
<Select value={presetDateRange} onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1W">일주일</SelectItem>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputDatePicker
|
||||
value={dateRange?.from}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, from: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
~
|
||||
<InputDatePicker
|
||||
value={dateRange?.to}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, to: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
<Label className="ml-4 mr-2 text-label">게시판</Label>
|
||||
<Select
|
||||
value={bbsId?.toString() || ""}
|
||||
@@ -281,7 +291,6 @@ function RecentPage() {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Label className="ml-4 mr-2 text-label">상태</Label>
|
||||
<Select value={delYn} onValueChange={(value) => setDelYn(value)}>
|
||||
<SelectTrigger className="w-[10rem]">
|
||||
@@ -292,7 +301,6 @@ function RecentPage() {
|
||||
<SelectItem value="Y">삭제</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Label className="ml-4 mr-2 text-label">검색</Label>
|
||||
<Select
|
||||
value={keywordType}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
InputDatePicker,
|
||||
Label,
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -28,7 +28,7 @@ import { useDataTable } from "@/shared/lib";
|
||||
import { columns } from "../model/table-columns";
|
||||
|
||||
type DateRange = {
|
||||
from: Date | undefined;
|
||||
from?: Date | undefined;
|
||||
to?: Date | undefined;
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ function UsersPage() {
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
});
|
||||
const [presetDateRange, setPresetDateRange] = useState("");
|
||||
const [dateRange, setDateRange] = useState<DateRange | undefined>({
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
@@ -167,6 +168,7 @@ function UsersPage() {
|
||||
default:
|
||||
presetDateRange = undefined;
|
||||
}
|
||||
setPresetDateRange(value);
|
||||
setDateRange(presetDateRange);
|
||||
};
|
||||
|
||||
@@ -277,23 +279,34 @@ function UsersPage() {
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="mr-2 text-label">기간</Label>
|
||||
<DateRangePicker value={dateRange} onValueChange={setDateRange}>
|
||||
<div className="card card-border flex items-center gap-2">
|
||||
<Select onValueChange={handlePresetDateRange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder="기간 선택"
|
||||
className="w-[24rem]"
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
<SelectItem value="6M">6개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</DateRangePicker>
|
||||
<Select
|
||||
value={presetDateRange}
|
||||
onValueChange={handlePresetDateRange}
|
||||
>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="기간 선택" className="w-[24rem]" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1M">1개월</SelectItem>
|
||||
<SelectItem value="3M">3개월</SelectItem>
|
||||
<SelectItem value="6M">6개월</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputDatePicker
|
||||
value={dateRange?.from}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, from: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
~
|
||||
<InputDatePicker
|
||||
value={dateRange?.to}
|
||||
onChange={(date) => {
|
||||
setDateRange({ ...dateRange, to: date });
|
||||
setPresetDateRange("");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -23,6 +23,7 @@ export const ENDPOINT = {
|
||||
PRIVACYLOG: "/user-service//api/v1/prvc-inq-logs",
|
||||
},
|
||||
CMS_SERVICE: {
|
||||
DASHBOARD: "/cms-service/api/v1/dashboard",
|
||||
SITES: "/cms-service/api/v1/sites",
|
||||
IPS: "/cms-service/api/v1/site-ips",
|
||||
DOMAINS: "/cms-service/api/v1/site-dmns",
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
NEXT_PUBLIC_SITE_ID = DM
|
||||
NEXT_PUBLIC_PORT = 3000
|
||||
NEXT_PUBLIC_PROXY_HOST = http://localhost:3000
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
|
||||
NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
|
||||
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
|
||||
NEXT_PUBLIC_CLAIM_NAME = Authorization
|
||||
NEXT_PUBLIC_AUTH_USER_ID = token-id
|
||||
NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
|
||||
|
||||
|
||||
NEXT_PUBLIC_PORT = 11035
|
||||
#NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035
|
||||
NEXT_PUBLIC_PROXY_HOST = http://localhost:3000/
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
|
||||
NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
|
||||
NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
|
||||
# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
NEXT_PUBLIC_SITE_ID = DM
|
||||
NEXT_PUBLIC_PORT = 3000
|
||||
NEXT_PUBLIC_PROXY_HOST = http://wr.hasuon.or.kr
|
||||
NEXT_PUBLIC_PORT = 11035
|
||||
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035
|
||||
NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/api/v1
|
||||
NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/operation-service/api/v1
|
||||
NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000
|
||||
|
||||
@@ -30,6 +30,9 @@ export default function Home() {
|
||||
<Button className='ml-5 place-self-auto'>
|
||||
<Link href="/pages/sdmMng/sdmApplyList">수요처 신청 현황(개발중)</Link>
|
||||
</Button>
|
||||
<Button className='ml-5 place-self-auto'>
|
||||
<Link href="/pages/map/mainMap">수요처 신청 현황(개발중)</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { SearchBar } from "@/components/ui/map/searchBar/searchBar";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const Map = () => {
|
||||
|
||||
const [selectDataPk, setSelectDataPk] = useState <string>('')
|
||||
|
||||
// 삭제요망 데이터 선ㅌ
|
||||
useEffect(() =>{
|
||||
console.log("Selected Data Pk: ", selectDataPk);
|
||||
},[selectDataPk])
|
||||
return (
|
||||
<>
|
||||
<SearchBar
|
||||
setHasuPk={(hasuPk: string) => {
|
||||
console.log("Selected HasuPk: ", hasuPk);
|
||||
if(hasuPk !==""){
|
||||
setSelectDataPk(hasuPk);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -0,0 +1,132 @@
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { mapService } from "../service";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
|
||||
const [checkedLayers, setCheckedLayers] = useState<string[]>([]);
|
||||
const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]);
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await mapService.getLyrStngs();
|
||||
setTreeData(response); // API 응답 저장
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 팝업 닫기 핸들러를 useCallback으로 메모이제이션
|
||||
const handlePopupClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
setFeatureData(null);
|
||||
}, []);
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// 체크된 레이어 정보를 받는 핸들러
|
||||
const handleCheckedLayersChange = (
|
||||
checkedIds: string[],
|
||||
checkedNodesData: any[]
|
||||
) => {
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
};
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
const onFeatureSelect = (data: any) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 항상 같은 select 객체 사용
|
||||
const select = GisApp.Module.select as any;
|
||||
if (!select) return;
|
||||
|
||||
// 1. 기존 선택 초기화
|
||||
select.resetSelectedFeature();
|
||||
|
||||
// 2. data가 OpenLayers Feature 인스턴스인지 확인
|
||||
let foundFeature: any = null;
|
||||
if (
|
||||
data &&
|
||||
typeof data.getGeometry === "function" &&
|
||||
typeof data.setStyle === "function"
|
||||
) {
|
||||
// 이미 Feature 인스턴스라면 그대로 사용
|
||||
foundFeature = data;
|
||||
} else {
|
||||
// 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용
|
||||
const featureId = data.fid || data.ftrIdn || data.id;
|
||||
map
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) foundFeature = feature;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 피처 선택
|
||||
if (foundFeature) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
select.selectFeature(foundFeature, true);
|
||||
} else {
|
||||
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
|
||||
}
|
||||
|
||||
setFeatureData(data);
|
||||
setIsOpen(true);
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={null}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
imgVisble={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -0,0 +1,94 @@
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
// 현재 문서의 스타일(CSS)을 새 창으로 복사하는 함수
|
||||
const copyStyles = (sourceDoc: Document, targetDoc: Document) => {
|
||||
Array.from(sourceDoc.styleSheets).forEach(styleSheet => {
|
||||
try {
|
||||
if (styleSheet.cssRules) { // <style> 태그로 주입된 CSS
|
||||
const newStyleEl = sourceDoc.createElement('style');
|
||||
Array.from(styleSheet.cssRules).forEach(cssRule => {
|
||||
newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
|
||||
});
|
||||
targetDoc.head.appendChild(newStyleEl);
|
||||
} else if (styleSheet.href) { // <link> 태그로 연결된 CSS 파일
|
||||
const newLinkEl = sourceDoc.createElement('link');
|
||||
newLinkEl.rel = 'stylesheet';
|
||||
newLinkEl.href = styleSheet.href;
|
||||
targetDoc.head.appendChild(newLinkEl);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('스타일시트를 복사할 수 없습니다:', e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
interface NewWindowPortalProps {
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
resizable?: string;
|
||||
scrollbars?: string;
|
||||
}
|
||||
|
||||
const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "" , width = 900, height = 800, resizable = "yes", scrollbars= "yes" }) => {
|
||||
const [container, setContainer] = useState<HTMLElement | null>(null);
|
||||
const newWindowRef = useRef<Window | null>(null);
|
||||
const onCloseRef = useRef(onClose);
|
||||
|
||||
// onClose 함수를 ref로 저장하여 의존성 문제 해결
|
||||
useEffect(() => {
|
||||
onCloseRef.current = onClose;
|
||||
}, [onClose]);
|
||||
|
||||
const handleUnload = useCallback(() => {
|
||||
onCloseRef.current();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// 이미 창이 열려있으면 새로 열지 않음
|
||||
if (newWindowRef.current && !newWindowRef.current.closed) return;
|
||||
|
||||
// 1. 새 창을 열고 참조를 저장합니다.
|
||||
const popupOptions = `width=${width},height=${height},resizable=${resizable},scrollbars=${scrollbars}`;
|
||||
newWindowRef.current = window.open('', title, popupOptions);
|
||||
|
||||
if (newWindowRef.current) {
|
||||
newWindowRef.current.document.title = title;
|
||||
|
||||
// 2. React 컴포넌트를 마운트할 컨테이너 div를 생성하여 새 창의 body에 추가합니다.
|
||||
const div = newWindowRef.current.document.createElement('div');
|
||||
newWindowRef.current.document.body.appendChild(div);
|
||||
setContainer(div);
|
||||
|
||||
// 3. 메인 창의 스타일을 새 창으로 복사합니다. (매우 중요)
|
||||
copyStyles(document, newWindowRef.current.document);
|
||||
|
||||
// 4. 사용자가 창을 닫을 때 부모에게 알리기 위한 이벤트 리스너를 설정합니다.
|
||||
// const handleUnload = () => {
|
||||
// onClose();
|
||||
// };
|
||||
newWindowRef.current.addEventListener('unload', handleUnload);
|
||||
|
||||
// 5. 컴포넌트가 사라질 때 창을 닫는 클린업 함수를 반환합니다.
|
||||
return () => {
|
||||
if (newWindowRef.current) {
|
||||
newWindowRef.current.removeEventListener('unload', handleUnload);
|
||||
if (!newWindowRef.current.closed) {
|
||||
newWindowRef.current.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
alert('팝업이 차단되었습니다. 팝업 차단을 해제하고 다시 시도해주세요.');
|
||||
onCloseRef.current();
|
||||
}
|
||||
}, [handleUnload, onClose, title]);
|
||||
|
||||
// `createPortal`을 사용해 children을 새 창의 container div에 렌더링합니다.
|
||||
return container ? createPortal(children, container) : null;
|
||||
};
|
||||
|
||||
export default NewWindowPortal;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './map'
|
||||
@@ -0,0 +1,46 @@
|
||||
|
||||
/**
|
||||
* 관망도 API
|
||||
*/
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
|
||||
|
||||
export type LyrStngProps = {
|
||||
lyr_sn: string
|
||||
lyr_nm: string
|
||||
upper_lyr_sn: string
|
||||
drawOrdr: string
|
||||
flterSql: string
|
||||
gobjTy: string
|
||||
lyrTpNm: string
|
||||
useYn: string
|
||||
minViewSize: string
|
||||
iconNm: string
|
||||
lblAtrbNm: string
|
||||
fontNm: string
|
||||
fontIntnColor: string
|
||||
fontExtnColor: string
|
||||
fontExtnWdth: string
|
||||
lnWdth: string
|
||||
lnColor: string
|
||||
polgnColor: string
|
||||
initEprssYn: string
|
||||
clickPsblYn: string
|
||||
}
|
||||
|
||||
export const mapService = {
|
||||
//레이어 설정 조회
|
||||
getLyrStngs: async () => {
|
||||
const response = await fetch(`${API_URL}/lyrStngs/WR_NETWORK`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { Toaster } from "react-hot-toast";
|
||||
import * as z from "zod";
|
||||
import SdmLogin from "../sdmLogin/page";
|
||||
|
||||
const createFormSchema = () => z.object({
|
||||
const sdmInfoFormSchema = z.object({
|
||||
sdmNm: z.string()
|
||||
.min(1, "수요처를 선택해주세요"),
|
||||
demandYmd: z.date().optional(),
|
||||
@@ -67,14 +67,14 @@ const createFormSchema = () => z.object({
|
||||
candidateTitle: z.string().optional(),
|
||||
});
|
||||
|
||||
export type FormData = z.infer<ReturnType<typeof createFormSchema>>;
|
||||
export type FormData = z.infer<typeof sdmInfoFormSchema>;
|
||||
|
||||
function SdmInfoInsertContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const {insertSdmInfoData, Sdm} = useDmStore((state) => state);
|
||||
const {successLogin} = Sdm;
|
||||
const [ismoneyDisabled, setIsmoneyDisabled] = useState(false);
|
||||
const formSchema = createFormSchema();
|
||||
const formSchema = sdmInfoFormSchema;
|
||||
|
||||
const [isLogin, setIsLogin] = useState(false);
|
||||
const [facilityName, setFacilityName] = useState<string>('');
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function MyForm() {
|
||||
useEffect(() => {
|
||||
if (successNextPage && formDataRef.current) {
|
||||
const data = formDataRef.current;
|
||||
console.log(data);
|
||||
const queryParams = new URLSearchParams({
|
||||
dmdCoNm: data.dmdCoNm || '',
|
||||
ctpvCd: data.ctpvCd || '',
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function WrAboutPage() {
|
||||
const [activeIdx, setActiveIdx] = useState(0);
|
||||
const navRefs = useRef<(HTMLLIElement | null)[]>([]);
|
||||
const cardRefs = useRef<(HTMLElement | null)[]>([]);
|
||||
|
||||
|
||||
// 클릭 시 이동
|
||||
const handleNavClick = (idx: number) => {
|
||||
setActiveIdx(idx);
|
||||
@@ -69,267 +69,269 @@ export default function WrAboutPage() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="relative w-[950px] mx-auto pt-[100px] font-sans">
|
||||
{/* 사이드바 */}
|
||||
<nav className="fixed left-[290px] top-[195px] w-[210px] z-50" aria-label="물재이용 정보 섹션 내비게이션">
|
||||
<ul role="list" className="relative space-y-6">
|
||||
{NAVS.map((nav, idx) => (
|
||||
<li
|
||||
key={nav.id}
|
||||
ref={(el) => { navRefs.current[idx] = el; }}
|
||||
role="listitem"
|
||||
tabIndex={0}
|
||||
aria-current={idx === activeIdx ? 'true' : 'false'}
|
||||
aria-label={`현재 위치: ${nav.label}`}
|
||||
onClick={() => handleNavClick(idx)}
|
||||
onKeyDown={(e) => handleKeyDown(e, idx)}
|
||||
className={`nav-item relative pl-6 text-2xl font-bold text-gray-700 cursor-pointer transition-all duration-300
|
||||
after:content-[''] after:absolute after:top-6 after:left-[0.5rem] after:w-[2px] after:h-[24px] after:bg-[#C5C5C5]
|
||||
${idx === activeIdx ? 'text-4xl font-bold text-gray-900' : ''}`}
|
||||
>
|
||||
<span className={`absolute left-[0.5rem] top-1 w-4 h-4 rounded-full -translate-x-1/2 transition-all duration-300
|
||||
<div className='bg-[#F2F2F2]'>
|
||||
<main className="relative w-[950px] mx-auto pt-[100px] font-sans">
|
||||
{/* 사이드바 */}
|
||||
<nav className="fixed left-[290px] top-[195px] w-[210px] z-50" aria-label="물재이용 정보 섹션 내비게이션">
|
||||
<ul role="list" className="relative space-y-6">
|
||||
{NAVS.map((nav, idx) => (
|
||||
<li
|
||||
key={nav.id}
|
||||
ref={(el) => { navRefs.current[idx] = el; }}
|
||||
role="listitem"
|
||||
tabIndex={0}
|
||||
aria-current={idx === activeIdx ? 'true' : 'false'}
|
||||
aria-label={`현재 위치: ${nav.label}`}
|
||||
onClick={() => handleNavClick(idx)}
|
||||
onKeyDown={(e) => handleKeyDown(e, idx)}
|
||||
className={`font-NotoSansKR nav-item relative pl-6 text-2xl font-bold text-[#383838] cursor-pointer transition-all duration-300
|
||||
after:content-[''] after:absolute after:top-6 after:left-[0.5rem] after:w-[2px] after:h-[24px] after:bg-[#C5C5C5] last:after:hidden
|
||||
${idx === activeIdx ? 'text-4xl font-bold text-[#383838]' : ''}`}
|
||||
>
|
||||
<span className={`absolute left-[0.5rem] top-1 w-4 h-4 rounded-full -translate-x-1/2 transition-all duration-300
|
||||
${idx === activeIdx ? 'bg-blue-500 border-blue-500' : 'bg-white border-gray-400 border'}`}></span>
|
||||
{nav.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
{nav.label}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<article ref={(el) => { cardRefs.current[0] = el; }} className="card-wrap pl-[100px] mb-24 !pt-0" id="card1" aria-labelledby="card1-title">
|
||||
<h2 id="card1-title" className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용이란
|
||||
</h2>
|
||||
<section aria-labelledby="card1-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card1-desc" className="text-[#269DDA] text-4xl font-bold mb-8">물 재이용이 무엇인가요?</h1>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">
|
||||
물 재이용은 빗물, 오수, 하수처리수, 폐수처리수 및 발전소 온배수를<br />
|
||||
물 재이용시설을 이용하여 처리하고, 그 처리된 물을 생활, 공업, 농업, 조경,<br />
|
||||
하천 유지 등의 용도로 이용하는 것을 말합니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_01_1.png"
|
||||
alt="이 이미지는 물 재이용 시스템의 개요를 나타냅니다. 가정, 공공기관, 하수처리장 등에서 발생한 생활하수, 빗물, 하수처리수를 물 재이용 시설에서 처리한 후, 청소나 화장실, 농업, 살수차, 하천 유지, 공장, 공원 및 골프장 등 다양한 용도로 활용하는 과정을 도식화한 것입니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-8 rounded"
|
||||
/>
|
||||
<p className="text-center">
|
||||
환경부는 2011년에 '물의 재이용 촉진 및 지원에 관한 법률'을 제정하여<br />
|
||||
한국환경공단과 함께 물 재이용 확대 및 촉진을 위해 노력하고 있습니다.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
<article ref={(el) => { cardRefs.current[0] = el; }} className="card-wrap pl-[100px] mb-24 !pt-0" id="card1" aria-labelledby="card1-title">
|
||||
<h2 id="card1-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용이란
|
||||
</h2>
|
||||
<section aria-labelledby="card1-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card1-desc" className="font-NotoSansKR text-[#269DDA] text-5xl font-bold mb-8">물 재이용이 무엇인가요?</h1>
|
||||
<p className="font-NotoSansKR text-[#231815] text-2xl mb-10 leading-relaxed">
|
||||
물 재이용은 빗물, 오수, 하수처리수, 폐수처리수 및 발전소 온배수를<br />
|
||||
물 재이용시설을 이용하여 처리하고, 그 처리된 물을 생활, 공업, 농업, 조경,<br />
|
||||
하천 유지 등의 용도로 이용하는 것을 말합니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_01_1.png"
|
||||
alt="이 이미지는 물 재이용 시스템의 개요를 나타냅니다. 가정, 공공기관, 하수처리장 등에서 발생한 생활하수, 빗물, 하수처리수를 물 재이용 시설에서 처리한 후, 청소나 화장실, 농업, 살수차, 하천 유지, 공장, 공원 및 골프장 등 다양한 용도로 활용하는 과정을 도식화한 것입니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-8 rounded"
|
||||
/>
|
||||
<p className="font-NotoSansKR text-center font-normal text-2xl">
|
||||
환경부는 2011년에 '물의 재이용 촉진 및 지원에 관한 법률'을 제정하여<br />
|
||||
한국환경공단과 함께 물 재이용 확대 및 촉진을 위해 노력하고 있습니다.
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
{/* 카드2 */}
|
||||
<article ref={(el) => { cardRefs.current[1] = el; }} className="card-wrap pl-[100px] mb-24" id="card2" aria-labelledby="card2-title">
|
||||
<h2 id="card2-title" className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용 필요성
|
||||
</h2>
|
||||
<section aria-labelledby="card2-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card2-desc" className="text-[#EE7B62] text-4xl font-bold mb-8">물 재이용, 왜 필요한가요?</h1>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">
|
||||
빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.<br />
|
||||
물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서<br />가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_02_1.png"
|
||||
alt="이 이미지는 기후변화로 인한 온도 상승 추이를 설명한다. 왼쪽에는 지구 그림과 온도계가 함께 표시되어 있으며, 지구 평균 기온은 지난 100년간 약 0.9℃ 상승했음을 나타낸다. 오른쪽에는 대한민국 지도와 함께 온도계가 있으며, 지난 100년간 약 1.8℃ 상승했음을 보여준다. 이는 지구 평균보다 2배 높은 수치이며, 자료 출처는 「기후변화 평가보고서 2020, 환경부·기상청」이다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-8 w-[80%]"
|
||||
/>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">
|
||||
- 우리나라의 기후변화 진행속도는 세계 평균을 상회하고 있습니다.<br />
|
||||
- 기후변화로 인한 국지적인 가뭄은 더욱 자주, 더욱 심하게 일어나고 있으며, 우리의 물 안보를 위협하고 있습니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_02_2.png"
|
||||
alt="세계 평균 1인당 강수량은 15,044m³이며, 대한민국은 2,546m³로 약 1/6 수준입니다. 계절별 강수량은 봄 10%, 여름 70%, 가을 15%, 겨울 5%입니다. 이는 여름철에 강수량이 집중되는 문제를 나타냅니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-8"
|
||||
/>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">
|
||||
- 우리나라의 1인당 강수량은 2,546㎥ 으로 세계 평균 15,044㎥의 약 1/6 수준입니다.<br />
|
||||
- 우리나라 강수량의 70%는 여름철에 집중되어 있어 이용 가능한 수자원확보가 어렵습니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_02_3.png"
|
||||
alt="물 재이용은 환경을 보호하며 지속 가능한 발전이 가능하게 하고, 안정적인 수자원 확보, 깨끗하고 경제적인 공업용수 공급, 하천 수질 개선을 통한 생태계 회복에 기여합니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[93%] mx-auto"
|
||||
/>
|
||||
</section>
|
||||
</article>
|
||||
{/* 카드2 */}
|
||||
<article ref={(el) => { cardRefs.current[1] = el; }} className="card-wrap pl-[100px] mb-24" id="card2" aria-labelledby="card2-title">
|
||||
<h2 id="card2-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용 필요성
|
||||
</h2>
|
||||
<section aria-labelledby="card2-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card2-desc" className="font-NotoSansKR text-[#ED5534] text-5xl font-semibold mb-8">물 재이용, 왜 필요한가요?</h1>
|
||||
<p className="font-NotoSansKR text-2xl font-normal mb-10 leading-relaxed">
|
||||
빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.<br />
|
||||
물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서<br />가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_02_1.png"
|
||||
alt="이 이미지는 기후변화로 인한 온도 상승 추이를 설명한다. 왼쪽에는 지구 그림과 온도계가 함께 표시되어 있으며, 지구 평균 기온은 지난 100년간 약 0.9℃ 상승했음을 나타낸다. 오른쪽에는 대한민국 지도와 함께 온도계가 있으며, 지난 100년간 약 1.8℃ 상승했음을 보여준다. 이는 지구 평균보다 2배 높은 수치이며, 자료 출처는 「기후변화 평가보고서 2020, 환경부·기상청」이다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-8 w-[80%]"
|
||||
/>
|
||||
<p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">
|
||||
- 우리나라의 기후변화 진행속도는 세계 평균을 상회하고 있습니다.<br />
|
||||
- 기후변화로 인한 국지적인 가뭄은 더욱 자주, 더욱 심하게 일어나고 있으며, 우리의 물 안보를 위협하고 있습니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_02_2.png"
|
||||
alt="세계 평균 1인당 강수량은 15,044m³이며, 대한민국은 2,546m³로 약 1/6 수준입니다. 계절별 강수량은 봄 10%, 여름 70%, 가을 15%, 겨울 5%입니다. 이는 여름철에 강수량이 집중되는 문제를 나타냅니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-8"
|
||||
/>
|
||||
<p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">
|
||||
- 우리나라의 1인당 강수량은 2,546㎥ 으로 세계 평균 15,044㎥의 약 1/6 수준입니다.<br />
|
||||
- 우리나라 강수량의 70%는 여름철에 집중되어 있어 이용 가능한 수자원확보가 어렵습니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_02_3.png"
|
||||
alt="물 재이용은 환경을 보호하며 지속 가능한 발전이 가능하게 하고, 안정적인 수자원 확보, 깨끗하고 경제적인 공업용수 공급, 하천 수질 개선을 통한 생태계 회복에 기여합니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[93%] mx-auto"
|
||||
/>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
{/* 카드3 */}
|
||||
<article ref={(el) => { cardRefs.current[2] = el; }} className="card-wrap pl-[100px] mb-24" id="card3" aria-labelledby="card3-title">
|
||||
<h2 id="card3-title" className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
빗물 재이용
|
||||
</h2>
|
||||
<section aria-labelledby="card3-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card3-desc" className="text-[#269DDA] text-4xl font-bold mb-6">친환경 수자원 빗물,<br />편리하게 사용할 수 있어요!</h1>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">가뭄을 극복하고 홍수를 예방하기 위해<br />비가 내릴 때 빗물을 모아두었다가 사용합니다.</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_03_1.png"
|
||||
alt="주택 지붕에 내린 빗물을 저장조에 모아 조경용수, 화장실용수, 세척·살수용수로 재활용하는 과정을 보여줍니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-6"
|
||||
/>
|
||||
<Image
|
||||
src="/img/wrAbout/img_03_2.png"
|
||||
alt="빗물이용"
|
||||
width={200}
|
||||
height={100}
|
||||
className="mb-5 w-[20%]"
|
||||
/>
|
||||
<p className="text-2xl font-bold leading-relaxed">
|
||||
- 의무 설치대상: 지붕면적 1,000㎡ 이상의 운동장, 체육관, 공공업무시설 등<br />
|
||||
- 설 치 기 준: 집수시설, 처리시설, 빗물 저류조(지붕 집수면적 x 0.05m 이상)<br />
|
||||
- 활 용 용 도: 골프장 등의 조경용수, 화장실용수, 세척·살수용수 등
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{/* 카드3 */}
|
||||
<article ref={(el) => { cardRefs.current[2] = el; }} className="card-wrap pl-[100px] mb-24" id="card3" aria-labelledby="card3-title">
|
||||
<h2 id="card3-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
빗물 재이용
|
||||
</h2>
|
||||
<section aria-labelledby="card3-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card3-desc" className="font-NotoSansKR leading-snug text-[#269DDA] text-5xl font-bold mb-6">친환경 수자원 빗물,<br />편리하게 사용할 수 있어요!</h1>
|
||||
<p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">가뭄을 극복하고 홍수를 예방하기 위해<br />비가 내릴 때 빗물을 모아두었다가 사용합니다.</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_03_1.png"
|
||||
alt="주택 지붕에 내린 빗물을 저장조에 모아 조경용수, 화장실용수, 세척·살수용수로 재활용하는 과정을 보여줍니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-6"
|
||||
/>
|
||||
<Image
|
||||
src="/img/wrAbout/img_03_2.png"
|
||||
alt="빗물이용"
|
||||
width={200}
|
||||
height={100}
|
||||
className="mb-5 w-[20%]"
|
||||
/>
|
||||
<p className="font-NotoSansKR text-2xl leading-relaxed">
|
||||
- 의무 설치대상: 지붕면적 1,000㎡ 이상의 운동장, 체육관, 공공업무시설 등<br />
|
||||
- 설 치 기 준: 집수시설, 처리시설, 빗물 저류조(지붕 집수면적 x 0.05m 이상)<br />
|
||||
- 활 용 용 도: 골프장 등의 조경용수, 화장실용수, 세척·살수용수 등
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
{/* 카드4 */}
|
||||
<article ref={(el) => { cardRefs.current[3] = el; }} className="card-wrap pl-[100px] mb-24" id="card4" aria-labelledby="card4-title">
|
||||
<h2 id="card4-title" className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
중수 재이용
|
||||
</h2>
|
||||
<section aria-labelledby="card4-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card4-desc" className="text-[#9771B0] text-4xl font-bold mb-6">요모조모 쓸모 많은 중수,<br />알뜰하게 재이용할 수 있어요!</h1>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">한번 사용한 물을 중수도 시설을 통해 재생하여<br />개별적 또는 지역적으로 재이용할 수 있습니다.</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_04_1.png"
|
||||
alt="욕실, 세면대, 주방, 세탁기 등에서 발생한 생활용수를 중수도 시설에서 처리하여 화장실 변기용수와 조경용수로 재이용하는 시스템 흐름도입니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-6"
|
||||
/>
|
||||
<Image
|
||||
src="/img/wrAbout/img_04_2.png"
|
||||
alt="중수재이용"
|
||||
width={200}
|
||||
height={100}
|
||||
className="mb-5 w-[20%]"
|
||||
/>
|
||||
<p className="text-2xl font-bold leading-relaxed">
|
||||
- 의무 설치대상: 일정규모 이상의 대규모점포, 숙박/목욕장, 공장, 각종 개발사업 등<br />
|
||||
- 의무 재이용량: 물 사용량의 10% 이상<br />
|
||||
- 활 용 용 도: 청소·화장실용수, 세척·살수용수, 조경용수, 공업용수 등
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{/* 카드4 */}
|
||||
<article ref={(el) => { cardRefs.current[3] = el; }} className="card-wrap pl-[100px] mb-24" id="card4" aria-labelledby="card4-title">
|
||||
<h2 id="card4-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
중수 재이용
|
||||
</h2>
|
||||
<section aria-labelledby="card4-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card4-desc" className="font-NotoSansKR leading-snug text-[#9771B0] text-5xl font-bold mb-6">요모조모 쓸모 많은 중수,<br />알뜰하게 재이용할 수 있어요!</h1>
|
||||
<p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">한번 사용한 물을 중수도 시설을 통해 재생하여<br />개별적 또는 지역적으로 재이용할 수 있습니다.</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_04_1.png"
|
||||
alt="욕실, 세면대, 주방, 세탁기 등에서 발생한 생활용수를 중수도 시설에서 처리하여 화장실 변기용수와 조경용수로 재이용하는 시스템 흐름도입니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-6"
|
||||
/>
|
||||
<Image
|
||||
src="/img/wrAbout/img_04_2.png"
|
||||
alt="중수재이용"
|
||||
width={200}
|
||||
height={100}
|
||||
className="mb-5 w-[20%]"
|
||||
/>
|
||||
<p className="font-NotoSansKR text-2xl leading-relaxed">
|
||||
- 의무 설치대상: 일정규모 이상의 대규모점포, 숙박/목욕장, 공장, 각종 개발사업 등<br />
|
||||
- 의무 재이용량: 물 사용량의 10% 이상<br />
|
||||
- 활 용 용 도: 청소·화장실용수, 세척·살수용수, 조경용수, 공업용수 등
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
{/* 카드5 */}
|
||||
<article ref={(el) => { cardRefs.current[4] = el; }} className="card-wrap pl-[100px] mb-24" id="card5" aria-labelledby="card5-title">
|
||||
<h2 id="card5-title" className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
하수 재이용
|
||||
</h2>
|
||||
<section aria-labelledby="card5-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card5-desc" className="text-[#00A5B1] text-4xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1>
|
||||
<p className="text-2xl font-bold mb-10 leading-relaxed">사용하고 버려져 공공하수처리장으로 모여든 물은<br />용도에 맞는 처리 후 재이용 할 수 있습니다.</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_05_1.png"
|
||||
alt="주택과 건물에서 발생한 하수가 공공하수처리장을 거쳐 처리된 후, 물 재이용 시설에서 정화되어 공업용수, 하천유지용수, 농업용수, 세척·살수용수로 재활용됩니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-6"
|
||||
/>
|
||||
<Image
|
||||
src="/img/wrAbout/img_05_2.png"
|
||||
alt="하수처리 재이용"
|
||||
width={200}
|
||||
height={100}
|
||||
className="mb-5 w-[20%]"
|
||||
/>
|
||||
<p className="text-2xl font-bold leading-relaxed">
|
||||
- 의무 이용대상: 공공하수처리장(시설용량 5,000㎥/일 이상)<br />
|
||||
- 의무 재이용량: 일 하수처리량의 10% 이상<br />
|
||||
- 활 용 용 도: 공업용수, 하천유지용수, 농업용수 등
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
{/* 카드5 */}
|
||||
<article ref={(el) => { cardRefs.current[4] = el; }} className="card-wrap pl-[100px] mb-24" id="card5" aria-labelledby="card5-title">
|
||||
<h2 id="card5-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
하수 재이용
|
||||
</h2>
|
||||
<section aria-labelledby="card5-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card5-desc" className="font-NotoSansKR leading-snug text-[#0099A4] text-5xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1>
|
||||
<p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">사용하고 버려져 공공하수처리장으로 모여든 물은<br />용도에 맞는 처리 후 재이용 할 수 있습니다.</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_05_1.png"
|
||||
alt="주택과 건물에서 발생한 하수가 공공하수처리장을 거쳐 처리된 후, 물 재이용 시설에서 정화되어 공업용수, 하천유지용수, 농업용수, 세척·살수용수로 재활용됩니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto mb-6"
|
||||
/>
|
||||
<Image
|
||||
src="/img/wrAbout/img_05_2.png"
|
||||
alt="하수처리 재이용"
|
||||
width={200}
|
||||
height={100}
|
||||
className="mb-5 w-[20%]"
|
||||
/>
|
||||
<p className="font-NotoSansKR text-2xl leading-relaxed">
|
||||
- 의무 이용대상: 공공하수처리장(시설용량 5,000㎥/일 이상)<br />
|
||||
- 의무 재이용량: 일 하수처리량의 10% 이상<br />
|
||||
- 활 용 용 도: 공업용수, 하천유지용수, 농업용수 등
|
||||
</p>
|
||||
</section>
|
||||
</article>
|
||||
|
||||
{/* 카드6 */}
|
||||
<article ref={(el) => { cardRefs.current[5] = el; }} className="card-wrap pl-[100px] mb-24" id="card6" aria-labelledby="card6-title">
|
||||
<h2 id="card6-title" className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용 목표
|
||||
</h2>
|
||||
<section aria-labelledby="card6-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card6-desc" className="text-[#269DDA] text-4xl font-bold mb-6">2030년 물 재이용 목표를 위해<br />모두의 관심과 노력이 필요합니다.</h1>
|
||||
<p className="text-2xl font-bold mb-6 leading-relaxed">
|
||||
환경부는 2030년까지의 제2차 물 재이용 기본계획을 수립하여<br />한국환경공단과 함께 물 재이용 확대 및 촉진을 위해 노력하고 있습니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_06_1.png"
|
||||
alt="지속가능한 물 재이용 정착으로 건전한 물순환 확산"
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[80%] mx-auto mb-6"
|
||||
/>
|
||||
<div className="flex gap-3 flex-wrap justify-between bg-gray-100 p-6 rounded-lg mb-8">
|
||||
<div className="flex-1">
|
||||
<Image
|
||||
src="/img/wrAbout/icon_01.png"
|
||||
alt=""
|
||||
width={70}
|
||||
height={70}
|
||||
className="mx-auto mb-4 w-[auto] h-[70px]"
|
||||
/>
|
||||
<h3 className="text-center font-semibold text-nowrap mb-2">도시 물 순환이용<br />체계로의 전환</h3>
|
||||
<ul className="text-sm text-gray-600 leading-relaxed space-y-1">
|
||||
<li className="text-nowrap">- 지속가능한 물 순환이용체계 구축</li>
|
||||
<li>- 물 재이용시설 관리체계 강화</li>
|
||||
</ul>
|
||||
{/* 카드6 */}
|
||||
<article ref={(el) => { cardRefs.current[5] = el; }} className="card-wrap pl-[100px] mb-24" id="card6" aria-labelledby="card6-title">
|
||||
<h2 id="card6-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용 목표
|
||||
</h2>
|
||||
<section aria-labelledby="card6-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] ">
|
||||
<h1 id="card6-desc" className="font-NotoSansKR leading-snug text-[#269DDA] text-5xl font-bold mb-6">2030년 물 재이용 목표를 위해<br />모두의 관심과 노력이 필요합니다.</h1>
|
||||
<p className="font-NotoSansKR text-2xl mb-6 leading-relaxed">
|
||||
환경부는 2030년까지의 제2차 물 재이용 기본계획을 수립하여<br />한국환경공단과 함께 물 재이용 확대 및 촉진을 위해 노력하고 있습니다.
|
||||
</p>
|
||||
<Image
|
||||
src="/img/wrAbout/img_06_1.png"
|
||||
alt="지속가능한 물 재이용 정착으로 건전한 물순환 확산"
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[80%] mx-auto mb-6"
|
||||
/>
|
||||
<div className="flex gap-3 flex-wrap justify-between bg-gray-100 p-6 rounded-lg mb-8">
|
||||
<div className="flex-1 text-center">
|
||||
<Image
|
||||
src="/img/wrAbout/icon_01.png"
|
||||
alt=""
|
||||
width={70}
|
||||
height={70}
|
||||
className="mx-auto mb-4 w-[auto] h-[70px]"
|
||||
/>
|
||||
<h3 className="font-NotoSansKR text-center font-medium text-nowrap mb-2 text-2xl">도시 물 순환이용<br />체계로의 전환</h3>
|
||||
<ul className="font-NotoSansKR text-xl text-gray-600 leading-relaxed space-y-1">
|
||||
<li className="text-nowrap">- 지속가능한 물 순환이용체계 구축</li>
|
||||
<li>- 물 재이용시설 관리체계 강화</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<Image
|
||||
src="/img/wrAbout/icon_02.png"
|
||||
alt=""
|
||||
width={70}
|
||||
height={70}
|
||||
className="mx-auto mb-4 w-[auto] h-[70px]"
|
||||
/>
|
||||
<h3 className="font-NotoSansKR text-center font-medium text-nowrap mb-2 text-2xl">유역기반 하수처리수 재이용<br />수요확대 및 공급체계 구축</h3>
|
||||
<ul className="font-NotoSansKR text-xl text-gray-600 leading-relaxed space-y-1">
|
||||
<li>- 유역기반 통합물관리 연계 강화</li>
|
||||
<li>- 하수처리수 재이용 공업용수 공급 활성화</li>
|
||||
<li>- 하수처리수 재이용을 활용한 물순환 촉진</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<Image
|
||||
src="/img/wrAbout/icon_03.png"
|
||||
alt=""
|
||||
width={70}
|
||||
height={70}
|
||||
className="mx-auto mb-4 w-[auto] h-[70px]"
|
||||
/>
|
||||
<h3 className="font-NotoSansKR text-center font-medium text-nowrap mb-2 text-2xl">물 재이용 인식제고<br />및 산업발전 기반 마련</h3>
|
||||
<ul className="font-NotoSansKR text-xl text-gray-600 leading-relaxed space-y-1">
|
||||
<li>- 물 재이용 교육 및 대국민 홍보 강화</li>
|
||||
<li>- 핵심기술 육성 및 산업발전 지원 강화</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<Image
|
||||
src="/img/wrAbout/icon_02.png"
|
||||
alt=""
|
||||
width={70}
|
||||
height={70}
|
||||
className="mx-auto mb-4 w-[auto] h-[70px]"
|
||||
/>
|
||||
<h3 className="text-center font-semibold text-nowrap mb-2">유역기반 하수처리수 재이용<br />수요확대 및 공급체계 구축</h3>
|
||||
<ul className="text-sm text-gray-600 leading-relaxed space-y-1">
|
||||
<li>- 유역기반 통합물관리 연계 강화</li>
|
||||
<li>- 하수처리수 재이용 공업용수 공급 활성화</li>
|
||||
<li>- 하수처리수 재이용을 활용한 물순환 촉진</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex-1 text-center">
|
||||
<Image
|
||||
src="/img/wrAbout/icon_03.png"
|
||||
alt=""
|
||||
width={70}
|
||||
height={70}
|
||||
className="mx-auto mb-4 w-[auto] h-[70px]"
|
||||
/>
|
||||
<h3 className="text-center font-semibold text-nowrap mb-2">물 재이용 인식제고<br />및 산업발전 기반 마련</h3>
|
||||
<ul className="text-sm text-gray-600 leading-relaxed space-y-1">
|
||||
<li>- 물 재이용 교육 및 대국민 홍보 강화</li>
|
||||
<li>- 핵심기술 육성 및 산업발전 지원 강화</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<Image
|
||||
src="/img/wrAbout/img_06_2.png"
|
||||
alt="물 재이용량은 2014년 13.3억톤에서 2030년 25.9억톤까지 증가할 것으로 예상됩니다. 그에 따른 기대 효과로는 연간 상수대체 17.9억톤, 물산업 시장성장 약 2조3,541억 원, 건천화된 73개 하천 해소, 약 20,550명의 고용 효과가 포함됩니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto"
|
||||
/>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
<Image
|
||||
src="/img/wrAbout/img_06_2.png"
|
||||
alt="물 재이용량은 2014년 13.3억톤에서 2030년 25.9억톤까지 증가할 것으로 예상됩니다. 그에 따른 기대 효과로는 연간 상수대체 17.9억톤, 물산업 시장성장 약 2조3,541억 원, 건천화된 73개 하천 해소, 약 20,550명의 고용 효과가 포함됩니다."
|
||||
width={800}
|
||||
height={400}
|
||||
className="w-[90%] mx-auto"
|
||||
/>
|
||||
</section>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,49 +2,49 @@
|
||||
|
||||
export default function WrExamplePage() {
|
||||
return (
|
||||
<div className="bg-[#F1F6F9]">
|
||||
<div className="bg-[#F2F2F2]">
|
||||
<div className="relative w-[950px] mx-auto pt-[50px] font-sans">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 rounded-full"></span>
|
||||
물 재이용사례
|
||||
</h2>
|
||||
</div>
|
||||
<div className="bg-[#F1F6F9] text-gray-800">
|
||||
<div className="bg-[#F2F2F2] text-gray-800">
|
||||
<div className="relative w-[950px] mx-auto pt-[50px] font-sans">
|
||||
{/* 카드 1 */}
|
||||
<div className="card-wrap mb-24" id="card1">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
사업사례 1
|
||||
</h2>
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]">
|
||||
{/* 사업명 */}
|
||||
<section>
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
경기권역 반도체 사업장 물 재이용 사업<br />
|
||||
: 삼성전자 반도체 사업장 하수재이용수 공급(재이용시설 설치, 공급망 구축 등)
|
||||
</p>
|
||||
</section>
|
||||
{/* 사업 배경 및 목적 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 배경 및 목적</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 배경 및 목적</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
삼성전자는 환경경영전략 실현과 반도체 공장의 안정적 용수 공급을 위해 하수 재이용수를 공업용수로 도입하기로 결정
|
||||
</p>
|
||||
</section>
|
||||
{/* 사업 주요 내용 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
1단계 사업[화성·오산(12만㎥/일) → 삼성 화성·기흥(29년 공급)]<br />
|
||||
2단계 사업[수원(21만㎥/일) → 삼성 평택(30년 공급)]
|
||||
</p>
|
||||
</section>
|
||||
{/* 특이사항 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
본 사업은 광역 하수 재이용 공급망 구축의 첫 사업으로 초기 민관협의체를 구성·운영 후 관계기관(환경부, 경기도, 지자체, 공단, 수공, 삼성전자) 간 MOU 체결을 통해 사업을 추진하였으며, 반도체 사업장에 하수 재이용수를 공업용수로 공급하는 국내 첫 사업으로 위협부담 해소를 위해 사업을 단계별로 시행
|
||||
</p>
|
||||
</section>
|
||||
@@ -63,15 +63,15 @@ export default function WrExamplePage() {
|
||||
|
||||
{/* 카드2 */}
|
||||
<div className="card-wrap mb-24" id="card2">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
사업사례 2
|
||||
</h2>
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]">
|
||||
{/* 사업명 */}
|
||||
<section>
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
경기도 파주시 하수재이용<br />
|
||||
: 운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에 공업용수 공급<br /><br />
|
||||
*LG디스플레이(P10공장), 희성전자, 엔비텍 등
|
||||
@@ -79,15 +79,15 @@ export default function WrExamplePage() {
|
||||
</section>
|
||||
{/* 사업 방식 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 방식</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 방식</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스)
|
||||
</p>
|
||||
</section>
|
||||
{/* 사업 주요 내용 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
처리공정 : 전처리분리막(U/F) + 역삼투서비(R/O)<br />
|
||||
공급량 : 4만m³/일<br />
|
||||
관로 : 13.6km<br />
|
||||
@@ -96,8 +96,8 @@ export default function WrExamplePage() {
|
||||
</section>
|
||||
{/* 특이사항 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례
|
||||
</p>
|
||||
<div className="grid grid-cols-1 gap-0 items-start">
|
||||
@@ -124,29 +124,29 @@ export default function WrExamplePage() {
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={3} className="bg-[#ffc000] h-[30px] text-xl text-white font-semibold text-center pt-[6px] pb-[6px] px-0">하수처리수 공급</td>
|
||||
<td colSpan={3} className="font-NotoSansKR bg-[#ffc000] h-[30px] text-xl text-white font-semibold text-center pt-[6px] pb-[6px] px-0">하수처리수 공급</td>
|
||||
</tr>
|
||||
<tr className="text-xl h-[30px]">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">위치</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">금촌하수</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">운정하수</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">위치</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">금촌하수</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">운정하수</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">시설용량</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">44,000㎥/일</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">60,000㎥/일</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">시설용량</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">44,000㎥/일</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">60,000㎥/일</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">유입량</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">27,000㎥/일<br />자연유하</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">30,000㎥/일<br />이송펌프장</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">유입량</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">27,000㎥/일<br />자연유하</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">30,000㎥/일<br />이송펌프장</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/* 첫 화살표 (자연유하 / 관로이송) */}
|
||||
<div className="flex h-[30px] justify-between items-center my-3 px-4">
|
||||
<div className="flex text-xl items-center gap-1 text-orange-600 font-bold pl-6">
|
||||
<span>자연유하: 161m</span>
|
||||
<span className="font-NotoSansKR">자연유하: 161m</span>
|
||||
<Image
|
||||
src="/img/wrExample/img_arrow_down_1.png"
|
||||
alt="오른쪽 화살표"
|
||||
@@ -163,7 +163,7 @@ export default function WrExamplePage() {
|
||||
height={24}
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
<span>관로이송: 3.2km</span>
|
||||
<span className="font-NotoSansKR">관로이송: 3.2km</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,26 +177,26 @@ export default function WrExamplePage() {
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={2} className="bg-[#00b050] text-xl h-[30px] text-white font-semibold text-center pt-[6px] pb-[6px] px-0">물재이용 처리시설</td>
|
||||
<td colSpan={2} className="font-NotoSansKR bg-[#00b050] text-xl h-[30px] text-white font-semibold text-center pt-[6px] pb-[6px] px-0">물재이용 처리시설</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">유입유량</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">57,000㎥/일</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">유입유량</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">57,000㎥/일</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">재이용시설</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">40,000㎥/일</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">재이용시설</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">40,000㎥/일</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">농축폐수처리</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">17,000㎥/일</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">농축폐수처리</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">17,000㎥/일</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/* 두 번째 화살표 (공급관로) */}
|
||||
<div className="flex flex-col items-center my-3">
|
||||
<div className="flex h-[30px] items-center gap-1 text-xl text-blue-600 font-semibold">
|
||||
<span>공급관로 L=10.4km</span>
|
||||
<span className="font-NotoSansKR">공급관로 L=10.4km</span>
|
||||
<Image
|
||||
src="/img/wrExample/img_arrow_down_2.png"
|
||||
alt="아래 화살표"
|
||||
@@ -217,15 +217,15 @@ export default function WrExamplePage() {
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={2} className="bg-[#3399ff] h-[30px] text-xl text-white font-semibold text-center pt-[6px] pb-[6px] px-0">수요처 공급</td>
|
||||
<td colSpan={2} className="font-NotoSansKR bg-[#3399ff] h-[30px] text-xl text-white font-semibold text-center pt-[6px] pb-[6px] px-0">수요처 공급</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">수요처</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">LG디스플레이 외 2개사</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">수요처</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">LG디스플레이 외 2개사</td>
|
||||
</tr>
|
||||
<tr className="text-sm">
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">수요량</td>
|
||||
<td className="text-xl h-[30px] border-black border p-1 font-bold">40,000㎥/일</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">수요량</td>
|
||||
<td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">40,000㎥/일</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -238,30 +238,30 @@ export default function WrExamplePage() {
|
||||
|
||||
{/* 카드3 */}
|
||||
<div className="card-wrap mb-24" id="card3">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
사업사례 3
|
||||
</h2>
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]">
|
||||
{/* 사업명 */}
|
||||
<section>
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
충남 보령 하수재이용<br />
|
||||
: 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진
|
||||
</p>
|
||||
</section>
|
||||
{/* 사업 방식 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 방식</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 방식</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결
|
||||
</p>
|
||||
</section>
|
||||
{/* 사업 주요 내용 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
처리공정 : 전처리분리막(U/F) + 역삼투서비(R/O)<br />
|
||||
공급량 : 1만m3/일<br />
|
||||
관로 : 8.7km<br />
|
||||
@@ -270,8 +270,8 @@ export default function WrExamplePage() {
|
||||
</section>
|
||||
{/* 특이사항 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU (환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕
|
||||
으로 추진 되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모
|
||||
</p>
|
||||
@@ -290,22 +290,22 @@ export default function WrExamplePage() {
|
||||
|
||||
{/* 카드4 */}
|
||||
<div className="card-wrap mb-24" id="card4">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
사업사례 4
|
||||
</h2>
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]">
|
||||
{/* 사업명 */}
|
||||
<section>
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
경기도 수원시 빗물이용과 중수도
|
||||
</p>
|
||||
</section>
|
||||
{/* 사업 내용 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 내용</h2>
|
||||
<p className="text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 내용</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed">
|
||||
(빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로 비산먼지 저감<br />
|
||||
※ 시설 가동중 도로 노면 온도의 7.2 ~ 26.3℃ 감소 효과 확인(21.7월, 수원시청).<br />
|
||||
다만, 가뭄기나 강우량이 적을 경우 빗물을 원활히 공급하기 다소 곤란<br /><br />
|
||||
@@ -320,7 +320,7 @@ export default function WrExamplePage() {
|
||||
height={250}
|
||||
className="w-[100%] mx-auto"
|
||||
/>
|
||||
<span className="text-xl block text-center font-semibold my-2"><월드컵경기장 인근도로 노면 살수></span>
|
||||
<span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><월드컵경기장 인근도로 노면 살수></span>
|
||||
</div>
|
||||
<div>
|
||||
<Image
|
||||
@@ -330,7 +330,7 @@ export default function WrExamplePage() {
|
||||
height={250}
|
||||
className="w-[100%] mx-auto"
|
||||
/>
|
||||
<span className="text-xl block text-center font-semibold my-2"><주유기 모양의 빗물 공급기></span>
|
||||
<span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><주유기 모양의 빗물 공급기></span>
|
||||
</div>
|
||||
<div>
|
||||
<Image
|
||||
@@ -340,7 +340,7 @@ export default function WrExamplePage() {
|
||||
height={250}
|
||||
className="w-[100%] mx-auto"
|
||||
/>
|
||||
<span className="text-xl block text-center font-semibold my-2"><빗물주유기에서 살수용수 공급></span>
|
||||
<span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><빗물주유기에서 살수용수 공급></span>
|
||||
</div>
|
||||
<div>
|
||||
<Image
|
||||
@@ -350,7 +350,7 @@ export default function WrExamplePage() {
|
||||
height={250}
|
||||
className="w-[100%] mx-auto"
|
||||
/>
|
||||
<span className="text-xl block text-center font-semibold my-2"><빗물 공급기 및 빗물분사장치 위치></span>
|
||||
<span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><빗물 공급기 및 빗물분사장치 위치></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
export default function WrOverviewPage() {
|
||||
return (
|
||||
<div className="bg-[#F1F6F9]">
|
||||
<div className="bg-[#F2F2F2]">
|
||||
<div className="relative w-[950px] mx-auto pt-[50px] font-sans">
|
||||
{/* 카드 1 - 물재이용 사업 추진 절차 */}
|
||||
<div className="card-wrap mb-24" id="card1">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용 사업 추진 절차
|
||||
</h2>
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]">
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] py-[50px]">
|
||||
<table className="table-fixed w-full text-center text-sm">
|
||||
<caption className="sr-only">물 재이용 추진 절차 상세 표</caption>
|
||||
<colgroup>
|
||||
@@ -22,55 +22,55 @@ export default function WrOverviewPage() {
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* 1단계 */}
|
||||
<tr><td></td><td></td><td className="text-xl bg-gray-100 font-bold py-3">타당성 조사</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">타당성 조사</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 2단계 */}
|
||||
<tr><td className="text-xl bg-yellow-400 text-black font-bold py-3">매칭시스템 신청</td><td></td><td className="text-xl bg-gray-100 font-bold py-3">사업계획(안) 수립</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">매칭시스템 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">사업계획(안) 수립</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 3단계 */}
|
||||
<tr><td className="text-xl bg-yellow-400 text-black font-bold py-3">하수도정보시스템 신청</td><td></td><td className="text-xl bg-gray-100 font-bold py-3">국고지원 신청</td><td className="text-lg">→</td><td className="text-xl bg-green-200 font-bold py-3">기술검토<br />(한국환경공단)</td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">하수도정보시스템 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">국고지원 신청</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 4단계 */}
|
||||
<tr><td></td><td></td><td className="text-xl bg-gray-100 font-bold py-3">국고지원 확정 통보</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">국고지원 확정 통보</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 5단계 */}
|
||||
<tr><td></td><td></td><td className="text-xl bg-gray-100 font-bold py-3">사업계획 수립(실시설계)</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">사업계획 수립(실시설계)</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 6단계 */}
|
||||
<tr><td className="text-xl bg-yellow-400 text-black font-bold py-3">(행정관) 기술검토 신청</td><td></td><td className="text-xl bg-gray-100 font-bold py-3">재원협의<br />(지방환경관서장)</td><td className="text-lg">→</td><td className="text-xl bg-green-200 font-bold py-3">기술검토<br />(한국환경공단)</td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">(행정관) 기술검토 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">재원협의(지방환경관서장)</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 7단계 */}
|
||||
<tr><td className="text-xl bg-yellow-400 text-black font-bold py-3">(행정관) 기술검토 신청</td><td></td><td className="text-xl bg-gray-100 font-bold py-3">설치승인<br />(시도지사)</td><td className="text-lg">→</td><td className="text-xl bg-green-200 font-bold py-3">기술검토<br />(한국환경공단)</td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">(행정관) 기술검토 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">설치승인(시도지사)</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 8단계 */}
|
||||
<tr><td></td><td></td><td className="text-xl bg-gray-100 font-bold py-3">공사시행</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="text-xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">공사시행</td><td></td><td></td></tr>
|
||||
<tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr>
|
||||
{/* 9단계 */}
|
||||
<tr><td className="text-xl bg-yellow-400 text-black font-bold py-3">(행정관) 기술검토 신청</td><td></td><td className="text-xl bg-gray-100 font-bold py-3">준공(국고정산)</td><td className="text-lg">→</td><td className="text-xl bg-green-200 font-bold py-3">기술검토<br />(한국환경공단)</td></tr>
|
||||
<tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">(행정관) 기술검토 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">준공(국고정산)</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 카드 2 - 물재이용 매칭 시스템 */}
|
||||
<div className="card-wrap mb-24" id="card2">
|
||||
<h2 className="relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<div className="mt-36 card-wrap mb-24" id="card2">
|
||||
<h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6">
|
||||
<span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span>
|
||||
물재이용 매칭 시스템이란?
|
||||
</h2>
|
||||
<div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]">
|
||||
{/* 개요 */}
|
||||
<section>
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">개요</h2>
|
||||
<p className="text-2xl font-bold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">개요</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-bold text-gray-800 leading-relaxed">
|
||||
기후변화 및 지자체의 개발속도 차이에 따라 물 자원의 수요-공급 불균형이 발생하여 빗물, 중수도, 하수처리 재처리수 등의 공급 여유가 있는 지자체의 재이용수를 부족한 지자체 수요처에 공급하여 불균형 해소를 통한 수자원 절약 및 물 자원 순환
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* 구성 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">구성</h2>
|
||||
<ul className="list-[square] pl-6 text-2xl font-bold text-gray-800 leading-relaxed space-y-2">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">구성</h2>
|
||||
<ul className=" font-NotoSansKR list-[square] pl-6 text-2xl font-bold text-gray-800 leading-relaxed space-y-2">
|
||||
<li>공급자: 하수처리장, 빗물이용시설, 중수도 등</li>
|
||||
<li>수요자: 산업체(냉각수), 공공시설(조경, 도로청소), 농민(농업용수) 등</li>
|
||||
<li>시스템: 수질, 수량, 거리 기반 매칭, 중개</li>
|
||||
@@ -80,16 +80,16 @@ export default function WrOverviewPage() {
|
||||
|
||||
{/* 절차 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="text-2xl font-bold text-gray-800 leading-relaxed">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2>
|
||||
<p className="font-NotoSansKR text-2xl font-bold text-gray-800 leading-relaxed">
|
||||
① 공급자 등록 → ② 수요자 조건 입력 → ③ 매칭 검토 → ④ 시스템 매칭 → ⑤ 계약·공급 이행
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* 기대효과 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">기대효과</h2>
|
||||
<ul className="list-[square] pl-6 text-2xl font-bold text-gray-800 leading-relaxed space-y-2">
|
||||
<h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">기대효과</h2>
|
||||
<ul className="font-NotoSansKR list-[square] pl-6 text-2xl font-bold text-gray-800 leading-relaxed space-y-2">
|
||||
<li>물 자원 수요-공급 조정을 통한 효율적 분배</li>
|
||||
<li>물 재이용시설 데이터 수집으로 체계적 관리</li>
|
||||
<li>지자체, 사업시행자 등 재이용 정보 획득 용이</li>
|
||||
@@ -102,7 +102,7 @@ export default function WrOverviewPage() {
|
||||
height={500}
|
||||
className="h-[500px] mx-auto mb-4 rounded"
|
||||
/>
|
||||
<p className="text-2xl font-bold text-gray-800 mt-10">(하수 재이용 시스템 개요도)</p>
|
||||
<p className="font-NotoSansKR text-2xl font-bold text-gray-800 mt-10">(하수 재이용 시스템 개요도)</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -395,7 +395,7 @@ const WrStatusPage: React.FC = () => {
|
||||
}, [selectedRegion]);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#F4F7FB] text-gray-800">
|
||||
<div className="flex h-screen bg-[#F2F2F2] text-gray-800">
|
||||
{/* 우측 콘텐츠 영역 */}
|
||||
<main className="flex-1 p-6 overflow-auto">
|
||||
{/* 상단 제목 */}
|
||||
@@ -455,19 +455,19 @@ const WrStatusPage: React.FC = () => {
|
||||
<div className="absolute bottom-2 right-2 bg-white rounded-lg shadow-md p-4 text-sm space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="inline-block w-4 h-4 rounded-sm" style={{backgroundColor: '#FF6B6B'}}></span>
|
||||
<span className="text-[12px] text-gray-900">하수재이용시설 (총 717건)</span>
|
||||
<span className="text-[12px] text-gray-900">하수재이용시설 (총 717개소수)</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="inline-block w-4 h-4 rounded-sm" style={{backgroundColor: '#4ECDC4'}}></span>
|
||||
<span className="text-[12px] text-gray-900">빗물이용시설 (총 3,730건)</span>
|
||||
<span className="text-[12px] text-gray-900">빗물이용시설 (총 3,730개소수)</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="inline-block w-4 h-4 rounded-sm" style={{backgroundColor: '#45B7D1'}}></span>
|
||||
<span className="text-[12px] text-gray-900">중수도시설 (총 1,074건)</span>
|
||||
<span className="text-[12px] text-gray-900">중수도시설 (총 1,074개소수)</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="inline-block w-4 h-4 rounded-sm" style={{backgroundColor: '#FED16A'}}></span>
|
||||
<span className="text-[12px] text-gray-900">온배수시설 (총 201건)</span>
|
||||
<span className="text-[12px] text-gray-900">온배수시설 (총 201개소수)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -522,7 +522,7 @@ const WrStatusPage: React.FC = () => {
|
||||
<div className="bg-white rounded-xl shadow border border-[#DBE2E6] overflow-hidden">
|
||||
<div className="border-b border-[#DBE2E6] px-4 py-3">
|
||||
<h3 className="text-[12px] font-semibold">재이용통계(2023)</h3>
|
||||
<p className="text-[12px] text-[#999] font-medium">항목별 통계 보기</p>
|
||||
<p className="text-[12px] text-[#999] font-medium">행정구역별 재이용시설 현황 개소수 그래프</p>
|
||||
</div>
|
||||
<div className="p-4 bg-[#f9fbfd]">
|
||||
<div className="h-[305px] text-gray-500">
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { getFetch } from "@/components/ui/api";
|
||||
import { DEV_URL_FO } from "@/components/ui/config";
|
||||
import type { FacilityInfo } from "./type";
|
||||
|
||||
const checkWrRegList = async () => {
|
||||
const wrRegList = await getFetch("wr", `/reuseFc/pubFc`);
|
||||
return wrRegList;
|
||||
};
|
||||
|
||||
const getRuseFclt = async (
|
||||
page: string,
|
||||
sggOrCity: string,
|
||||
searchTxt?: string
|
||||
) => {
|
||||
const checkWrRegListRes = await checkWrRegList();
|
||||
|
||||
let url = `/prcsFclt/ruseInfo/${page ? page : 1}/11/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`;
|
||||
|
||||
console.log(' checkWrRegListRes ', checkWrRegListRes)
|
||||
const ruseList = checkWrRegListRes.data.filter((item: string) => item !== "");
|
||||
|
||||
if (searchTxt) {
|
||||
url = url + `/${searchTxt}`;
|
||||
}
|
||||
|
||||
const response = await fetch(DEV_URL_FO + url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(ruseList),
|
||||
});
|
||||
|
||||
const hasuList = await response.json().then((res: any) => {
|
||||
|
||||
const data =res.data
|
||||
.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
fctCpc: Number(item.fctCpc) || 0,
|
||||
searchNm: item.fctNm,
|
||||
searchPk: item.bscFctNm,
|
||||
|
||||
};
|
||||
})
|
||||
.filter((hasu) =>
|
||||
ruseList.find((ruse) => hasu.bscFctCd === ruse)
|
||||
);
|
||||
return{ data: data, count: res.count };
|
||||
|
||||
});
|
||||
return hasuList
|
||||
};
|
||||
|
||||
export type DmMapActions = {
|
||||
findRuseFclt(sggOrCity:string, searchTxt: string): void;
|
||||
};
|
||||
|
||||
export type DmMapType = {
|
||||
map: {
|
||||
ruseFcltList: FacilityInfo[];
|
||||
};
|
||||
};
|
||||
|
||||
export const dmMapState: DmMapType = {
|
||||
map: {
|
||||
ruseFcltList: [],
|
||||
},
|
||||
};
|
||||
export type DmMapStore = DmMapType & DmMapActions;
|
||||
|
||||
export const DmMapReducer = (set: any): DmMapActions => {
|
||||
return {
|
||||
findRuseFclt: async (sggOrCity:string, searchTxt: string) => {
|
||||
const ruseFcltList = await getRuseFclt('1', sggOrCity,searchTxt);
|
||||
|
||||
set((state: DmMapType) => ({
|
||||
map: {
|
||||
...state.map,
|
||||
ruseFcltList :ruseFcltList.data
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
export type FacilityInfo= {
|
||||
bscFctCd: string; // 기본시설 코드
|
||||
bscFctNm: string; // 기본시설명
|
||||
rvbsnCd: string; // 유역 코드
|
||||
rvbsn: string; // 유역명
|
||||
lgbsn: string; // 대권역명
|
||||
mdmbsn: string; // 중권역명
|
||||
rgnCd: string; // 관할청 코드
|
||||
rgnNm: string; // 관할청 명칭
|
||||
adstDstrCd: string; // 행정구역 코드
|
||||
sido: string; // 시도
|
||||
gugun: string; // 구군
|
||||
addr: string; // 주소
|
||||
fctCpc: string; // 시설용량
|
||||
efflWqltStndrdCd: string; // 방류수 수질기준 코드
|
||||
efflWqltStndrdNm: string; // 방류수 수질기준명
|
||||
trtFrfNm: string; // 처리공법명
|
||||
trtDstr: string; // 처리구역
|
||||
rnDt: string; // 사용개시일 (예: YYYYMMDD)
|
||||
cmplDt: string; // 준공일자 (예: YYYYMMDD)
|
||||
trstOprEtpNm: string; // 위탁운영기관명
|
||||
bscFctClsNm: string; // 기본시설 구분명
|
||||
sitArea: string; // 부지면적
|
||||
trtDstrArea: string; // 처리구역 면적
|
||||
swrTrtPopl: string; // 하수처리 인구
|
||||
mnstr: string; // 총괄 유역
|
||||
efflWtrrgn: string; // 방류수역
|
||||
rvNm: string; // 하천명
|
||||
bscFctClsCd: string; // 기본시설 구분 코드
|
||||
rvbsnNm: string; // 유역명
|
||||
tel: string; // 전화번호
|
||||
rswtrYn: string; // 재이용수 여부 ("Y" 또는 "N")
|
||||
ruse: boolean; // 재이용 여부 (boolean)
|
||||
}
|
||||
@@ -387,11 +387,9 @@ export const SdmState: SdmType = {
|
||||
similarCaseList: similarCaseList,
|
||||
upHeadMoneyDetailList : moneyDetailModalHeader,
|
||||
moneyDetailList: moneyDetailList,
|
||||
|
||||
// 수요정보 신청현황
|
||||
sdmInfoStatusList: [],
|
||||
upHeadSdmInfoStatus: upHeadSdmInfoStatus,
|
||||
|
||||
// 로그인관련
|
||||
successNextPage : false,
|
||||
successLogin: false,
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import { createStore } from 'zustand/vanilla';
|
||||
import { CommonReducer, CommonState, type CommonStore, type CommonType } from './common';
|
||||
import { SdmReducer, SdmState, type SdmStore, type SdmType } from './sdmMng';
|
||||
import { dmMapState, type DmMapType, type DmMapStore, DmMapReducer } from './map';
|
||||
import { tokenManager } from '@/src/entities/auth/lib/token-manager';
|
||||
|
||||
export type DmStore = CommonStore & SdmStore;
|
||||
export type DmState = CommonType & SdmType;
|
||||
export type DmStore = CommonStore & SdmStore &DmMapStore ;
|
||||
export type DmState = CommonType & SdmType &DmMapType;
|
||||
|
||||
export const StoreZus = (initState: DmState= {
|
||||
...CommonState,
|
||||
...SdmState,
|
||||
...dmMapState,
|
||||
}) => {
|
||||
return createStore<DmStore>()((set) => {
|
||||
tokenManager.setToken("site-id-key", 'DM');
|
||||
tokenManager.setToken("site-theme-key", 'green');
|
||||
|
||||
|
||||
return {
|
||||
...initState,
|
||||
...CommonReducer(set),
|
||||
...SdmReducer(set)
|
||||
...SdmReducer(set),
|
||||
...DmMapReducer(set)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
type SearchType = {
|
||||
searchNm: string;
|
||||
searchPk: string;
|
||||
};
|
||||
type thisProps = {
|
||||
label: string | undefined;
|
||||
initValue: string | undefined;
|
||||
setValue: (searchNm?: string) => void;
|
||||
setRtnData: (rtnData: any) => void;
|
||||
searchList?: any[];
|
||||
};
|
||||
export default function SearchInputText({
|
||||
label = undefined,
|
||||
initValue = undefined,
|
||||
setValue = () => {},
|
||||
setRtnData = () => {},
|
||||
searchList = undefined,
|
||||
}: thisProps) {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [textValue, setTextValue] = useState(initValue || "");
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
|
||||
setTextValue(value);
|
||||
setValue(value);
|
||||
if (value && value!=='') {
|
||||
setIsOpen(true);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const handleSuggestionClick = useCallback(
|
||||
(searchNm: string) => {
|
||||
// Do not update textValue to keep input unchanged on suggestion click
|
||||
setValue(searchNm);
|
||||
setIsOpen(false);
|
||||
setRtnData(searchList?.find((item) => item.searchNm === searchNm) || {});
|
||||
},
|
||||
[searchList, setRtnData, setValue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
wrapperRef.current &&
|
||||
!wrapperRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="relative">
|
||||
<input
|
||||
type="text"
|
||||
value={textValue ||''}
|
||||
onChange={handleInputChange}
|
||||
/* onFocus={() => setIsOpen(true)} */
|
||||
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={`${label}을(를) 입력하세요`}
|
||||
/>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 z-50 mb-2 max-h-72 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
|
||||
<div className="max-h-72">
|
||||
{searchList && searchList.length > 0 ? (
|
||||
searchList.map((item: SearchType, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
className="w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl hover:bg-gray-100"
|
||||
onClick={() => handleSuggestionClick(item.searchNm)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
handleSuggestionClick(item.searchNm);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.searchPk?.substring(0, 10)}
|
||||
<span className="float-right">{item.searchNm}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
|
||||
검색 결과가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
"use client";
|
||||
import { useDmStore } from "@/app/store";
|
||||
import {
|
||||
SearchDiv,
|
||||
SearchFormLeft,
|
||||
SearchFormRight,
|
||||
SelectBox,
|
||||
type SelectDataType,
|
||||
} from "@common/business_components/ui";
|
||||
import { Label } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import SearchInputText from "../SearchInputText/SearchInputText";
|
||||
|
||||
export function SearchBar({
|
||||
|
||||
setHasuPk
|
||||
}:{
|
||||
setHasuPk: (hasuPk: string) => void,
|
||||
}
|
||||
|
||||
) {
|
||||
const {map, Common, initializeCommonData ,findRuseFclt} = useDmStore((state) => state);
|
||||
const [sidoValue, setSidoValue] = useState<string>("");
|
||||
const [sigunValue, setSigunValue] = useState<string>("");
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "전체", val: "0" },
|
||||
]);
|
||||
useEffect(() => {
|
||||
initializeCommonData();
|
||||
}, [initializeCommonData]);
|
||||
|
||||
const initializeSigunData = useCallback(
|
||||
(sidoCd: string) => {
|
||||
if (sidoCd === "0") {
|
||||
setSigunValue("0");
|
||||
setFilteredSigunData([{ text: "전체", val: "0" }]);
|
||||
} else {
|
||||
const newFilteredData = [
|
||||
{ text: "전체", val: "0" },
|
||||
...Common.selectSigunData.filter(
|
||||
(item: SelectDataType) => item.val.substring(0, 2) === sidoCd
|
||||
),
|
||||
];
|
||||
setSigunValue("0");
|
||||
setFilteredSigunData(newFilteredData);
|
||||
}
|
||||
},
|
||||
[Common.selectSigunData]
|
||||
);
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
switch (type) {
|
||||
case "ctpvCd":
|
||||
initializeSigunData(val);
|
||||
setSidoValue(val);
|
||||
break;
|
||||
case "sggCd":
|
||||
setSigunValue(val);
|
||||
}
|
||||
},
|
||||
[initializeSigunData]
|
||||
);
|
||||
|
||||
|
||||
useEffect(()=>{
|
||||
console.log('map.ruseFcltList ', map.ruseFcltList)
|
||||
},[map.ruseFcltList])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-4 gap-4 ">
|
||||
<div className="col-span-4">
|
||||
<SearchDiv>
|
||||
<SearchFormLeft>
|
||||
<div className="col-span-10 flex flex-row">
|
||||
<SelectBox
|
||||
className="col-span-2 w-1/6 h-full text-2xl"
|
||||
label="시도"
|
||||
selectClass={""}
|
||||
selectArray={Common.selectSidoData || []}
|
||||
onSelectValue={(val) =>
|
||||
onSelectValue(val.toString(), "ctpvCd")
|
||||
}
|
||||
value={sidoValue|| Common.selectSidoData[0]?.val || ""}
|
||||
/>
|
||||
<SelectBox
|
||||
className="col-span-2 w-1/6 h-full text-2xl ml-2"
|
||||
label="시군구"
|
||||
selectClass={""}
|
||||
selectArray={filteredSigunData || []}
|
||||
onSelectValue={(val) =>
|
||||
onSelectValue(val.toString(), "sggCd")
|
||||
}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<div className="col-span-6 flex flex-row items-center min-w-[40rem]">
|
||||
<Label className="text-xl justify-self-center mt-2 mx-3">
|
||||
하수재이용시설 :
|
||||
</Label>
|
||||
<SearchInputText
|
||||
label={'시설명'}
|
||||
initValue={undefined}
|
||||
setValue={ (searchNm?: string)=> {
|
||||
let sggOrCity=''
|
||||
if(searchNm){
|
||||
if(sigunValue === "0" || sigunValue === "") {
|
||||
sggOrCity = sidoValue;
|
||||
}else{
|
||||
sggOrCity = sigunValue;
|
||||
}
|
||||
findRuseFclt(sggOrCity, searchNm);
|
||||
}
|
||||
}}
|
||||
setRtnData={(rtnData: any)=> {
|
||||
setHasuPk(rtnData.bscFctCd || "");
|
||||
}}
|
||||
searchList={map.ruseFcltList || []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SearchFormLeft>
|
||||
<SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
|
||||
<div className="flex flex-row space-x-2">
|
||||
{/* <Button className="mt-2" size={"sm"} onClick={() => {}}>
|
||||
조회
|
||||
</Button> */}
|
||||
</div>
|
||||
</SearchFormRight>
|
||||
</SearchDiv>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
import ModalSimilarPage from "@/app/pages/sdmMng/sdmInfoInsert/modalSimilar/page";
|
||||
import type { FormData } from "@/app/pages/sdmMng/sdmInfoInsert/page";
|
||||
import { useDmStore } from "@/app/store";
|
||||
import { CANDIDATE, DatePicker, FormField, FormLabel, FormUi, SelectBox, UiModal } from "@common/business_components";
|
||||
import {
|
||||
CANDIDATE,
|
||||
DatePicker,
|
||||
FormField,
|
||||
FormLabel,
|
||||
FormUi,
|
||||
SelectBox,
|
||||
UiModal,
|
||||
} from "@common/business_components";
|
||||
import { Input } from "@common/components";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
@@ -10,6 +18,8 @@ import { AccessibleFormField } from "./AccessibleFormField";
|
||||
import type { ControllerRenderProps, UseFormReturn } from "react-hook-form";
|
||||
import SearchInputText from "../common/searhDemandInput/component";
|
||||
import type { DemandCompType } from "@/app/store/sdmMng";
|
||||
import NewWindowPortal from "@/app/pages/map/popup/windowPotal";
|
||||
import Map from "@/app/pages/map/popup/sdmInfoMap";
|
||||
|
||||
export interface SdmInfoRegFormProps {
|
||||
form: UseFormReturn<FormData>;
|
||||
@@ -17,21 +27,31 @@ export interface SdmInfoRegFormProps {
|
||||
facilityName?: string;
|
||||
}
|
||||
|
||||
type FieldComponent = (field: ControllerRenderProps<FormData, keyof FormData>) => JSX.Element;
|
||||
type FieldComponent = (
|
||||
field: ControllerRenderProps<FormData, keyof FormData>
|
||||
) => JSX.Element;
|
||||
|
||||
export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfoRegFormProps) {
|
||||
export function SdmInfoRegForm({
|
||||
form,
|
||||
onCheckboxChange,
|
||||
facilityName,
|
||||
}: SdmInfoRegFormProps) {
|
||||
const { initializeCommonData, Sdm } = useDmStore((state) => state);
|
||||
const { useTypesData } = Sdm;
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const [sdmNmValue, setSdmNmValue] = useState<string>(searchParams.get('dmdCoNm') || '');
|
||||
|
||||
const [sdmNmValue, setSdmNmValue] = useState<string>(
|
||||
searchParams.get("dmdCoNm") || ""
|
||||
);
|
||||
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false);
|
||||
const [isSimilarCase, setIsSimilarCase] = useState(false);
|
||||
const [key, setKey] = useState(0);
|
||||
// 후보지 팝업 오픈 상태
|
||||
const [isCandidatePopup, setIsCandidatePopup] = useState(false);
|
||||
|
||||
//모달 조회에 필요한 데이터
|
||||
const [sply, setSply] = useState<number>(0);
|
||||
const [usageCd, setUsageCd] = useState<string>('청소');
|
||||
const [usageCd, setUsageCd] = useState<string>("청소");
|
||||
const [money, setMoney] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,7 +61,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
useEffect(() => {
|
||||
if (useTypesData && useTypesData.length > 0) {
|
||||
setTimeout(() => {
|
||||
form.setValue('usePurpose', useTypesData[0]?.val?.toString() || '');
|
||||
form.setValue("usePurpose", useTypesData[0]?.val?.toString() || "");
|
||||
}, 500);
|
||||
}
|
||||
}, [useTypesData, form]);
|
||||
@@ -49,32 +69,45 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
useEffect(() => {
|
||||
if (facilityName) {
|
||||
setSdmNmValue(facilityName);
|
||||
form.setValue('sdmNm', facilityName);
|
||||
setKey(prev => prev + 1);
|
||||
form.setValue("sdmNm", facilityName);
|
||||
setKey((prev) => prev + 1);
|
||||
}
|
||||
}, [facilityName, form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get("dmdCoNm")) {
|
||||
form.setValue("sdmNm", searchParams.get("dmdCoNm") || "");
|
||||
setSdmNmValue(searchParams.get("dmdCoNm") || "");
|
||||
setKey((prev) => prev + 1);
|
||||
}
|
||||
}, [searchParams, form]);
|
||||
|
||||
const searchCandidate = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
console.log(1);
|
||||
setIsCandidatePopup(true);
|
||||
};
|
||||
|
||||
const searchSimilarCase = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSimilarCase(true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handleCheckboxChange = (checked: boolean) => {
|
||||
setIsBznmTotNoDisabled(checked);
|
||||
onCheckboxChange?.(checked);
|
||||
};
|
||||
|
||||
// 시설 선택시 이벤트
|
||||
const selectComp = useCallback((rtnData: DemandCompType[]) => {
|
||||
const rtnCompData = rtnData?.[0];
|
||||
form.setValue('sdmNm', rtnCompData?.dmdCoNm || '');
|
||||
}, [form]);
|
||||
const selectComp = useCallback(
|
||||
(rtnData: DemandCompType[]) => {
|
||||
const rtnCompData = rtnData?.[0];
|
||||
form.setValue("sdmNm", rtnCompData?.dmdCoNm || "");
|
||||
},
|
||||
[form]
|
||||
);
|
||||
|
||||
const formFields: Array<{
|
||||
name: keyof FormData;
|
||||
@@ -93,15 +126,15 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
<SearchInputText
|
||||
label="수요처명"
|
||||
setValue={(val) => {
|
||||
setSdmNmValue(val || '');
|
||||
field.onChange(val || '');
|
||||
setSdmNmValue(val || "");
|
||||
field.onChange(val || "");
|
||||
}}
|
||||
setRtnData={selectComp}
|
||||
initValue={facilityName || sdmNmValue}
|
||||
{...field}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "demandYmd",
|
||||
@@ -110,7 +143,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<div className="w-full">
|
||||
<DatePicker
|
||||
<DatePicker
|
||||
mode="single"
|
||||
selected={field.value as Date}
|
||||
onSelect={(date) => {
|
||||
@@ -122,12 +155,12 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
value: field.value as Date,
|
||||
onChange: (date: Date | undefined) => {
|
||||
field.onChange(date as Date);
|
||||
}
|
||||
},
|
||||
}}
|
||||
selectClass={undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "demandQnty",
|
||||
@@ -135,8 +168,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 0,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="요구수량(㎥/d)을 입력하세요"
|
||||
<Input
|
||||
placeholder="요구수량(㎥/d)을 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -148,7 +181,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "usePurpose",
|
||||
@@ -158,16 +191,16 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
component: (field) => (
|
||||
<SelectBox
|
||||
selectClass={undefined}
|
||||
label=""
|
||||
selectArray={useTypesData}
|
||||
label=""
|
||||
selectArray={useTypesData}
|
||||
onSelectValue={(val) => {
|
||||
field.onChange(val.toString());
|
||||
setUsageCd(val.toString());
|
||||
}}
|
||||
className=""
|
||||
className=""
|
||||
value={field.value as string}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "money",
|
||||
@@ -201,14 +234,14 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
</FormUi>
|
||||
}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "reqWaterQlty",
|
||||
label: "요구수질",
|
||||
formIndex: 0,
|
||||
required: true,
|
||||
component: undefined
|
||||
component: undefined,
|
||||
},
|
||||
{
|
||||
name: "grssClfg",
|
||||
@@ -216,8 +249,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="총대장균군수를 입력하세요"
|
||||
<Input
|
||||
placeholder="총대장균군수를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -226,7 +259,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "tn",
|
||||
@@ -234,8 +267,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="총질소를 입력하세요"
|
||||
<Input
|
||||
placeholder="총질소를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -244,7 +277,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "combResdch",
|
||||
@@ -252,8 +285,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="결합잔류염소를 입력하세요"
|
||||
<Input
|
||||
placeholder="결합잔류염소를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -262,7 +295,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "tph",
|
||||
@@ -270,8 +303,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="총인을 입력하세요"
|
||||
<Input
|
||||
placeholder="총인을 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -280,16 +313,16 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "trbt",
|
||||
label: "탁도(NTU)",
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="탁도를 입력하세요"
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="탁도를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -298,7 +331,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "ph",
|
||||
@@ -306,8 +339,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="수소이온농도를 입력하세요"
|
||||
<Input
|
||||
placeholder="수소이온농도를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -316,7 +349,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "bod",
|
||||
@@ -324,8 +357,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="생물학적 산소요구량을 입력하세요"
|
||||
<Input
|
||||
placeholder="생물학적 산소요구량을 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -334,7 +367,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "clIonQnt",
|
||||
@@ -342,8 +375,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="염화물을 입력하세요"
|
||||
<Input
|
||||
placeholder="염화물을 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -352,16 +385,16 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "smll",
|
||||
label: "냄새",
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="냄새를 입력하세요"
|
||||
<Input
|
||||
placeholder="냄새를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -370,7 +403,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "cdtnv",
|
||||
@@ -378,8 +411,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="전기전도도를 입력하세요"
|
||||
<Input
|
||||
placeholder="전기전도도를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -388,7 +421,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "crmty",
|
||||
@@ -396,8 +429,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="색도를 입력하세요"
|
||||
<Input
|
||||
placeholder="색도를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -406,16 +439,15 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "rmrk",
|
||||
label: "비고",
|
||||
formIndex: 1,
|
||||
required: true,
|
||||
component: (field) => (
|
||||
<Input
|
||||
placeholder="비고를 입력하세요"
|
||||
<Input
|
||||
placeholder="비고를 입력하세요"
|
||||
type="text"
|
||||
className="!text-xl text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -424,7 +456,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "candidateTitle",
|
||||
@@ -433,16 +465,16 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
component: (field) => (
|
||||
<div>
|
||||
<div className="-mt-14 float-right flex gap-10">
|
||||
<Button className={'bg-gray-200'} onClick={searchCandidate}>
|
||||
<Button className={"bg-gray-200"} onClick={searchCandidate}>
|
||||
후보지 조회
|
||||
</Button>
|
||||
<Button className={'bg-gray-200'} onClick={searchSimilarCase}>
|
||||
<Button className={"bg-gray-200"} onClick={searchSimilarCase}>
|
||||
유사사례
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex justify-center gap-10 mt-5">
|
||||
<Input
|
||||
placeholder="후보지1"
|
||||
<Input
|
||||
placeholder="후보지1"
|
||||
type="text"
|
||||
className="!text-xl w-[130px] text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -451,8 +483,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
<Input
|
||||
placeholder="후보지2"
|
||||
<Input
|
||||
placeholder="후보지2"
|
||||
type="text"
|
||||
className="!text-xl w-[130px] text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -461,8 +493,8 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
/>
|
||||
<Input
|
||||
placeholder="후보지3"
|
||||
<Input
|
||||
placeholder="후보지3"
|
||||
type="text"
|
||||
className="!text-xl w-[130px] text-gray-600"
|
||||
value={field.value as string}
|
||||
@@ -473,24 +505,32 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const normalFields = formFields.filter(f => f.formIndex === 0 && f.component);
|
||||
const groupFields = formFields.filter(f => f.formIndex === 1 && f.component);
|
||||
const candidateFields = formFields.filter(f => f.formIndex === 2 && f.component);
|
||||
const normalFields = formFields.filter(
|
||||
(f) => f.formIndex === 0 && f.component
|
||||
);
|
||||
const groupFields = formFields.filter(
|
||||
(f) => f.formIndex === 1 && f.component
|
||||
);
|
||||
const candidateFields = formFields.filter(
|
||||
(f) => f.formIndex === 2 && f.component
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{normalFields.map(({ name, label, component, required }) => (
|
||||
{normalFields.map(({ name, label, component, required }) =>
|
||||
component ? (
|
||||
<FormField
|
||||
key={name}
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
name === 'money' ? component(field) : (
|
||||
render={({ field }) =>
|
||||
name === "money" ? (
|
||||
component(field)
|
||||
) : (
|
||||
// <FormUi label={label}>
|
||||
// {component(field)}
|
||||
// </FormUi>
|
||||
@@ -502,10 +542,10 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
inputComponent={component(field)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
))}
|
||||
)}
|
||||
|
||||
{groupFields.length > 0 && (
|
||||
<div className="mt-10">
|
||||
@@ -535,7 +575,7 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
</div>
|
||||
)}
|
||||
|
||||
{candidateFields.map(({ name, label, component, required }, index) => (
|
||||
{candidateFields.map(({ name, label, component, required }, index) =>
|
||||
component ? (
|
||||
<FormField
|
||||
key={name + index}
|
||||
@@ -555,11 +595,13 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
)}
|
||||
/>
|
||||
) : null
|
||||
))}
|
||||
)}
|
||||
|
||||
{isSimilarCase && (
|
||||
<UiModal
|
||||
body={<ModalSimilarPage sply={sply} usageCd={usageCd} money={money} />}
|
||||
body={
|
||||
<ModalSimilarPage sply={sply} usageCd={usageCd} money={money} />
|
||||
}
|
||||
bodyClass="sm:max-w-[100vw] w-[1400px] h-[900px]"
|
||||
open={isSimilarCase}
|
||||
onOpenChange={(open) => !open && setIsSimilarCase(false)}
|
||||
@@ -567,6 +609,20 @@ export function SdmInfoRegForm({ form, onCheckboxChange, facilityName }: SdmInfo
|
||||
noFooterYn="Y"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 후보지 조회 새 창 포털 */}
|
||||
{isCandidatePopup && (
|
||||
<NewWindowPortal
|
||||
onClose={() => setIsCandidatePopup(false)}
|
||||
title="공급처 후보지 조회"
|
||||
width={window.screen.availWidth}
|
||||
height={window.screen.availHeight}
|
||||
resizable="no"
|
||||
scrollbars="yes"
|
||||
>
|
||||
<Map />
|
||||
</NewWindowPortal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,33 @@ import { BZNM_TOT_NO, FormField, FormUi } from "@common/business_components";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { sha512 } from 'js-sha512';
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { FormProvider, useForm, type ControllerRenderProps } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
export function SdmLoginForm() {
|
||||
type FormData = z.infer<ReturnType<typeof createFormSchema>>;
|
||||
type FieldComponent = (field: ControllerRenderProps<FormData, keyof FormData>) => JSX.Element;
|
||||
const createFormSchema = () => z.object({
|
||||
const { actionLogin, initializeCommonData } = useDmStore((state) => state);
|
||||
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false);
|
||||
|
||||
const formatBusinessNumber = (value: string) => {
|
||||
// 숫자만 추출
|
||||
const digitsOnly = value.replace(/\D/g, "");
|
||||
|
||||
// 10자리까지만 처리
|
||||
const limitedDigits = digitsOnly.slice(0, 10);
|
||||
|
||||
// 3-2-5 형식으로 포맷팅
|
||||
if (limitedDigits.length <= 3) {
|
||||
return limitedDigits;
|
||||
} else if (limitedDigits.length <= 5) {
|
||||
return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3)}`;
|
||||
} else {
|
||||
return `${limitedDigits.slice(0, 3)}-${limitedDigits.slice(3, 5)}-${limitedDigits.slice(5)}`;
|
||||
}
|
||||
};
|
||||
|
||||
// 스키마를 useMemo로 최적화
|
||||
const sdmLoginFormSchema = useMemo(() => z.object({
|
||||
dmdInfoId: z.string()
|
||||
.min(1, "사업자등록번호를 입력해주세요")
|
||||
.regex(/^\d{10}$/, "사업자등록번호는 10자리 숫자만 입력 가능합니다"),
|
||||
@@ -24,11 +43,12 @@ export function SdmLoginForm() {
|
||||
.max(16, "비밀번호는 16자 이내로 입력해주세요"),
|
||||
rgtr: z.string().optional(),
|
||||
dmdAplyPswd: z.string().optional(),
|
||||
});
|
||||
}), [isBznmTotNoDisabled]);
|
||||
|
||||
const { actionLogin, initializeCommonData } = useDmStore((state) => state);
|
||||
const [isBznmTotNoDisabled, setIsBznmTotNoDisabled] = useState(false);
|
||||
const formSchema = createFormSchema();
|
||||
type FormData = z.infer<typeof sdmLoginFormSchema>;
|
||||
type FieldComponent = (field: ControllerRenderProps<FormData, keyof FormData>) => JSX.Element;
|
||||
|
||||
const formSchema = sdmLoginFormSchema;
|
||||
|
||||
useEffect(() => {
|
||||
initializeCommonData();
|
||||
@@ -76,7 +96,12 @@ export function SdmLoginForm() {
|
||||
placeholder="사업자등록번호를 입력하세요"
|
||||
type="text"
|
||||
className="!mt-5 !mb-5 !text-xl text-gray-600"
|
||||
{...field}
|
||||
{...field}
|
||||
value={formatBusinessNumber(field.value as string)}
|
||||
onChange={(e) => {
|
||||
const dmdId = e.target.value.replace(/\D/g, "").slice(0, 10);
|
||||
field.onChange(dmdId);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -27,13 +27,17 @@ export function middleware(req: NextRequest) {
|
||||
.map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
|
||||
.join("&");
|
||||
|
||||
console.log(' sesesesesesevereveraserwereqwrewbrtkehjwrtg server')
|
||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||
decodedParams ? `?${decodedParams}` : ""
|
||||
}`;
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
if (pathname === "/") {
|
||||
console.log(" maim 화면이동 ");
|
||||
return NextResponse.redirect(new URL("/pages/wrStatus", req.url));
|
||||
}
|
||||
|
||||
|
||||
if (pathname.startsWith("/attach-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
@@ -124,5 +128,5 @@ export function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/server/:path*", "/user-service/:path*", "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*"],
|
||||
matcher: ["/server/:path*", "/user-service/:path*", "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*", "/"],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 961 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1016 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 468 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 757 B |
|
After Width: | Height: | Size: 785 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 791 B |
|
After Width: | Height: | Size: 455 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 782 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 217 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1009 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1009 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.0 KiB |