Compare commits
466
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60ee194cc9 | ||
|
|
3d027ed374 | ||
|
|
d70eb17a72 | ||
|
|
ddd619babc | ||
|
|
5d27ee830e | ||
|
|
212b9a43a9 | ||
|
|
f092a23ee4 | ||
|
|
25c357a423 | ||
|
|
a77246a1db | ||
|
|
f00ce31648 | ||
|
|
dc57a491dd | ||
|
|
c9b7266959 | ||
|
|
0dd2111cb9 | ||
|
|
c39b048398 | ||
|
|
87fbf15e2b | ||
|
|
db1cfaa1a6 | ||
|
|
905b750877 | ||
|
|
be14fe2603 | ||
|
|
d7f101ee30 | ||
|
|
20f7d595a1 | ||
|
|
e909dc81ad | ||
|
|
a510779ea1 | ||
|
|
0818e9ade2 | ||
|
|
e60a8bed8b | ||
|
|
17fdcd7d1f | ||
|
|
6412e1a997 | ||
|
|
1674598480 | ||
|
|
23ebf36244 | ||
|
|
342db06685 | ||
|
|
35b9f813c4 | ||
|
|
aafb326662 | ||
|
|
aa1b309b26 | ||
|
|
2c355525df | ||
|
|
bd20a499d3 | ||
|
|
5fec5d5239 | ||
|
|
a14b22b1ef | ||
|
|
50aa549311 | ||
|
|
bf0aef59cd | ||
|
|
b2ddcd0cf3 | ||
|
|
572ef04bb4 | ||
|
|
110d6c689c | ||
|
|
f40d6fd5e8 | ||
|
|
49ace44c9b | ||
|
|
cc3bc548c9 | ||
|
|
cc3d7ccd7b | ||
|
|
43bc7db7d5 | ||
|
|
b0672aead5 | ||
|
|
086d5a18e8 | ||
|
|
91576f401b | ||
|
|
febcd18eda | ||
|
|
8cc9f0d291 | ||
|
|
41643563f8 | ||
|
|
b6b58eaeba | ||
|
|
e8a5701e73 | ||
|
|
a8f2c8f41a | ||
|
|
241222dcd1 | ||
|
|
380575dae0 | ||
|
|
4d685d5a67 | ||
|
|
96196229f4 | ||
|
|
5e54039028 | ||
|
|
94b2e84c10 | ||
|
|
80d2230c9a | ||
|
|
4d20a68f93 | ||
|
|
90a1d6fdff | ||
|
|
00e1cb707e | ||
|
|
6eeb774a21 | ||
|
|
6c4b8a52dc | ||
|
|
a7e0d8f826 | ||
|
|
d1ccddc1d3 | ||
|
|
d5f0c3bc95 | ||
|
|
056643bd96 | ||
|
|
01d5ed297e | ||
|
|
dae5d0a9f5 | ||
|
|
41ad33310a | ||
|
|
b38ee9c7c7 | ||
|
|
9f7f1db9b8 | ||
|
|
b7134cb3e4 | ||
|
|
ec78658e46 | ||
|
|
44361af392 | ||
|
|
76130caa6d | ||
|
|
352a78a0c5 | ||
|
|
a2a165a42b | ||
|
|
0f27c1e5ce | ||
|
|
e944ad4369 | ||
|
|
65bee9586d | ||
|
|
31cda8579a | ||
|
|
1dc40b9efc | ||
|
|
6cb3d8ad65 | ||
|
|
225e2732d1 | ||
|
|
768ad001d2 | ||
|
|
866d11988d | ||
|
|
d82ae99b6a | ||
|
|
eb4f6f1b09 | ||
|
|
23341b93c7 | ||
|
|
b99f4904b0 | ||
|
|
17b1c12286 | ||
|
|
c79824e358 | ||
|
|
f742feca4b | ||
|
|
de8971bea6 | ||
|
|
569e003827 | ||
|
|
c47c130fc3 | ||
|
|
2db5dd59af | ||
|
|
8a8504dca9 | ||
|
|
7e82c6a802 | ||
|
|
01377b6013 | ||
|
|
b38be38169 | ||
|
|
b9ac4a1c18 | ||
|
|
bdc6251474 | ||
|
|
479cf5c4a0 | ||
|
|
dbea497cc5 | ||
|
|
94af73a810 | ||
|
|
a7fd6a8e16 | ||
|
|
2b3502161c | ||
|
|
8039ab8281 | ||
|
|
0544a72011 | ||
|
|
34ba09ce1b | ||
|
|
2bfe653ac0 | ||
|
|
aa5a71ae68 | ||
|
|
9067e645b3 | ||
|
|
10d3fc096a | ||
|
|
7fd95ba6f9 | ||
|
|
b4982e07b8 | ||
|
|
351e1091ce | ||
|
|
e707637c88 | ||
|
|
5da89e8ee7 | ||
|
|
794691f05e | ||
|
|
e58b84a01b | ||
|
|
3d51f003c0 | ||
|
|
b5a99e5f59 | ||
|
|
afca685fd4 | ||
|
|
f9ff279895 | ||
|
|
4cde9b67c1 | ||
|
|
261941452a | ||
|
|
a860650dfb | ||
|
|
63d2dd1f4f | ||
|
|
8d765c2825 | ||
|
|
80d93c1631 | ||
|
|
872b3d08c0 | ||
|
|
47cb11d07b | ||
|
|
8576ef83b5 | ||
|
|
cb191817a8 | ||
|
|
8198fda45b | ||
|
|
37374cb754 | ||
|
|
e80b8953a0 | ||
|
|
9e36edec7f | ||
|
|
3204bba71b | ||
|
|
3cdcf6a6f7 | ||
|
|
1d1141b59a | ||
|
|
8fd1d59fe2 | ||
|
|
922861c872 | ||
|
|
4a3945f1e4 | ||
|
|
a1caa1b860 | ||
|
|
166dea2b86 | ||
|
|
3f7262e213 | ||
|
|
231ee10ff8 | ||
|
|
5aa9ab4f39 | ||
|
|
8c46f7ca06 | ||
|
|
23e15daa37 | ||
|
|
dd56ed60ad | ||
|
|
18129905d8 | ||
|
|
f54ce13711 | ||
|
|
fb12eadf65 | ||
|
|
bb3179f0e4 | ||
|
|
e8b309460e | ||
|
|
ed0736dcde | ||
|
|
edc5bb52af | ||
|
|
37ac927c45 | ||
|
|
d03263a3c6 | ||
|
|
be46bcbd17 | ||
|
|
bf9a34cbe8 | ||
|
|
201dbf577f | ||
|
|
fc961e2946 | ||
|
|
e401455f31 | ||
|
|
254986b48b | ||
|
|
4c1615853c | ||
|
|
884dd71ca7 | ||
|
|
70efdc9db5 | ||
|
|
36549e7281 | ||
|
|
c1ffc33834 | ||
|
|
f67e065c5e | ||
|
|
18a11ab5fa | ||
|
|
6a7eb1f584 | ||
|
|
214ff1a435 | ||
|
|
d7cc579335 | ||
|
|
2c3961644c | ||
|
|
9b53c1ae08 | ||
|
|
4840a95097 | ||
|
|
08a7a4d6bc | ||
|
|
c02ac6f425 | ||
|
|
2fe3631a0d | ||
|
|
e0cdba00be | ||
|
|
5e206dd210 | ||
|
|
2008a5a9ee | ||
|
|
8bda55c653 | ||
|
|
53aeeccc2a | ||
|
|
c7738a6d6c | ||
|
|
baf29df950 | ||
|
|
0a13f34092 | ||
|
|
0b45361135 | ||
|
|
cff7b7da30 | ||
|
|
83420c76b8 | ||
|
|
42f83bcd24 | ||
|
|
197c2583ed | ||
|
|
10f5d42122 | ||
|
|
816b6b2ca8 | ||
|
|
ccfbe6ae32 | ||
|
|
64ed53cf4d | ||
|
|
c8e9fc54ed | ||
|
|
6927f77f86 | ||
|
|
9c86058fe1 | ||
|
|
8126fb844a | ||
|
|
23c9ddf4ce | ||
|
|
c4291ceda5 | ||
|
|
8dfd7cc290 | ||
|
|
e5498ef04b | ||
|
|
7baebf2cbc | ||
|
|
a4c14433cd | ||
|
|
63388ccc90 | ||
|
|
043fd3d4a8 | ||
|
|
bda3228d0f | ||
|
|
eda0b6b813 | ||
|
|
6dd09971f1 | ||
|
|
95ca40913f | ||
|
|
23424b5eee | ||
|
|
8bfe1c9ab2 | ||
|
|
4bd5276fc5 | ||
|
|
92ed3233af | ||
|
|
2c1e4d18fc | ||
|
|
3f190dfff6 | ||
|
|
6cc02da7a4 | ||
|
|
0dc981819f | ||
|
|
c87ccf8788 | ||
|
|
dcaa2d5ef3 | ||
|
|
e0715ce177 | ||
|
|
3333f1c8e6 | ||
|
|
4b7ada9362 | ||
|
|
71c5dfb8dd | ||
|
|
7e3911dbd6 | ||
|
|
839d31b9ca | ||
|
|
8e757ab9db | ||
|
|
de7e66afb4 | ||
|
|
63050b0efa | ||
|
|
386fdade59 | ||
|
|
0cbbc3b934 | ||
|
|
c4592a026a | ||
|
|
c981b83284 | ||
|
|
927bb7cb3a | ||
|
|
725976b5c0 | ||
|
|
cdbe6a855f | ||
|
|
a1c96e9e98 | ||
|
|
1488805242 | ||
|
|
cf681125a6 | ||
|
|
7dcef46829 | ||
|
|
36a7654973 | ||
|
|
5b12f81b8f | ||
|
|
7c88b609f7 | ||
|
|
f5051225ea | ||
|
|
5af4f0f27b | ||
|
|
f7218fc98a | ||
|
|
cf115b769f | ||
|
|
774144846d | ||
|
|
3b2f6c386d | ||
|
|
987e5fcbfd | ||
|
|
335872eaec | ||
|
|
df24e7c2a8 | ||
|
|
3b5fee047b | ||
|
|
9bcc8b95e7 | ||
|
|
c9e3ff0d91 | ||
|
|
474ad1ab26 | ||
|
|
9ea3474d43 | ||
|
|
a26bd5ea5b | ||
|
|
4ee0eab653 | ||
|
|
fe92ba3c27 | ||
|
|
099e000f88 | ||
|
|
7fa4538667 | ||
|
|
91e11a7857 | ||
|
|
e8870d3bcb | ||
|
|
d582293e5f | ||
|
|
9152782a65 | ||
|
|
0dc378285c | ||
|
|
d8c5344451 | ||
|
|
d23a16905d | ||
|
|
2649ce286b | ||
|
|
2f9e97b65b | ||
|
|
403946c50a | ||
|
|
bdc7c64c61 | ||
|
|
181730bcde | ||
|
|
d79e0e585a | ||
|
|
678af2f06e | ||
|
|
dbda77a75b | ||
|
|
0459c0b231 | ||
|
|
e1940043e9 | ||
|
|
7f1c804a0c | ||
|
|
d07bb6074d | ||
|
|
82d67bb4c6 | ||
|
|
ed54fe9646 | ||
|
|
85ef1805e5 | ||
|
|
4109691eea | ||
|
|
4eaac5dbf1 | ||
|
|
07a625e03b | ||
|
|
b99fe8daf1 | ||
|
|
3f4e3581dd | ||
|
|
f00627270f | ||
|
|
ee19f9ca59 | ||
|
|
b9e9814664 | ||
|
|
8303fa3860 | ||
|
|
6d9042ac0f | ||
|
|
26f64988ef | ||
|
|
12a0a37d24 | ||
|
|
c8c11b19de | ||
|
|
45a500817f | ||
|
|
8a352a8e69 | ||
|
|
999fd610af | ||
|
|
dd6aff5ebf | ||
|
|
bc3c841b89 | ||
|
|
99ea453d23 | ||
|
|
71389a104c | ||
|
|
6e2ebe9025 | ||
|
|
29e39f98af | ||
|
|
5192f3e3c0 | ||
|
|
95c8416754 | ||
|
|
b102c4feba | ||
|
|
daff1610e3 | ||
|
|
054dcc4eee | ||
|
|
1f1f72451b | ||
|
|
1858739bb9 | ||
|
|
3d320d0e19 | ||
|
|
9d2c0afb98 | ||
|
|
ae51b8273f | ||
|
|
defe2a8e9b | ||
|
|
28aa932676 | ||
|
|
4aa2d0a62c | ||
|
|
29f2a9169e | ||
|
|
493acc9ebd | ||
|
|
1d8d9c7b3d | ||
|
|
d3bdc58994 | ||
|
|
20efccb4d9 | ||
|
|
f58daf3bac | ||
|
|
83de317308 | ||
|
|
84b1fd07ae | ||
|
|
b42810d29a | ||
|
|
e105f52fea | ||
|
|
2e6a75141b | ||
|
|
f637f096f9 | ||
|
|
7f497f60af | ||
|
|
7a019b063b | ||
|
|
e5e20a789a | ||
|
|
8883f5ba55 | ||
|
|
d64a694e20 | ||
|
|
a88f8ab33e | ||
|
|
5e3d263750 | ||
|
|
70136aa65d | ||
|
|
b514bdc472 | ||
|
|
5031f0c582 | ||
|
|
25287dc3d9 | ||
|
|
5527ad8c41 | ||
|
|
75525ec7a8 | ||
|
|
21e967473b | ||
|
|
c67b5844b6 | ||
|
|
ced62ec0c4 | ||
|
|
84bbaa09da | ||
|
|
dfa35542c5 | ||
|
|
a6348050fd | ||
|
|
4d700c3566 | ||
|
|
f2d7e947d7 | ||
|
|
3c65e6f8f5 | ||
|
|
ce60e5f5fb | ||
|
|
c9d85a8ce4 | ||
|
|
1efe2dbe89 | ||
|
|
b2260e0b61 | ||
|
|
3c2ca24dea | ||
|
|
2acbeb83d0 | ||
|
|
e0956e05a6 | ||
|
|
e67492853c | ||
|
|
0f8b6cd6d0 | ||
|
|
b11658e1d6 | ||
|
|
bcf5d3caec | ||
|
|
474676af17 | ||
|
|
36e56dcaef | ||
|
|
e42e28d062 | ||
|
|
0786053022 | ||
|
|
e56938f7ff | ||
|
|
791ec1ae2d | ||
|
|
3cc0e9de14 | ||
|
|
b8b0b24b0e | ||
|
|
ff7be74437 | ||
|
|
f52f22e80f | ||
|
|
86e00d48dc | ||
|
|
afbebb9dfc | ||
|
|
bf61f6f76a | ||
|
|
62c41297c4 | ||
|
|
4e4942313a | ||
|
|
09b5cc3798 | ||
|
|
fe76864a05 | ||
|
|
155a727a77 | ||
|
|
a52dacf500 | ||
|
|
e8e38310ee | ||
|
|
2652e2cfe0 | ||
|
|
5a76ae8a88 | ||
|
|
7d9b97aac9 | ||
|
|
69c3183e35 | ||
|
|
e6fcbfbde4 | ||
|
|
ed4d1b73cb | ||
|
|
5e6477cddd | ||
|
|
c13cab394a | ||
|
|
73f1940cb0 | ||
|
|
8a35d232ef | ||
|
|
728981cfe6 | ||
|
|
11cb2b9044 | ||
|
|
e3dac52d7e | ||
|
|
d1d8a1eff3 | ||
|
|
cae9f94388 | ||
|
|
4502d55ce1 | ||
|
|
cad87496ac | ||
|
|
c1d6cd7b8e | ||
|
|
477426c384 | ||
|
|
3d3b978070 | ||
|
|
117082c862 | ||
|
|
bfa03aabc0 | ||
|
|
f535cd1010 | ||
|
|
b388edb23c | ||
|
|
2f6d4ebec1 | ||
|
|
1c3cc5b61b | ||
|
|
db7584cb01 | ||
|
|
ec60a05b01 | ||
|
|
b842408f41 | ||
|
|
6e33a62ed9 | ||
|
|
36231a5eca | ||
|
|
90f0e4691b | ||
|
|
b41a317788 | ||
|
|
84fb81b245 | ||
|
|
e18f759fab | ||
|
|
ce9c7b17a2 | ||
|
|
c2d6f7fe37 | ||
|
|
9b18aa9cef | ||
|
|
5eccb58aa9 | ||
|
|
acce1364fa | ||
|
|
625c5f5fe2 | ||
|
|
dffa9d74a0 | ||
|
|
668a8ef844 | ||
|
|
df4d708981 | ||
|
|
c48a46c7ab | ||
|
|
da844fb3a7 | ||
|
|
3e1f89d90f | ||
|
|
559e4ee352 | ||
|
|
a1d43dc65c | ||
|
|
d26cd90b02 | ||
|
|
45f72f7333 | ||
|
|
196d31a084 | ||
|
|
aaf898fb4c | ||
|
|
533e89c919 | ||
|
|
2f0448a98e | ||
|
|
aab8cf9622 | ||
|
|
5e0693e057 | ||
|
|
1708555133 | ||
|
|
c38d95fd60 | ||
|
|
667f7b2094 | ||
|
|
d17909df41 | ||
|
|
88113fca1c | ||
|
|
81fc947c9d | ||
|
|
c684440e65 | ||
|
|
4c6222acf1 | ||
|
|
2131ecc315 | ||
|
|
1d3910c157 | ||
|
|
3b2f5c2d5d | ||
|
|
386fc61a3f |
@@ -19,11 +19,15 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"]
|
||||||
"@common/components": ["../../packages/common/components/dist/"],
|
|
||||||
"@common/network": ["../../packages/common/network/"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"../../packages/common/types/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules", ".next", "dist", "coverage", "storybook-static", "public"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import "@/app/styles/globals.css";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "디지털 행정지원 시스템",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html lang="ko">
|
||||||
|
<body>{children}</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { DashboardPage } from "@/pages/dashboard/ui/dashboard-page";
|
||||||
|
|
||||||
|
export default DashboardPage;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { DesignEditPage } from "@/pages/design-cnstn";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
return <DesignEditPage id={id} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { DesignDetailGate } from "@/pages/design-cnstn";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
return <DesignDetailGate id={id} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { DesignListPage } from "@/pages/design-cnstn";
|
||||||
|
|
||||||
|
export default DesignListPage;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { DesignRegistPage } from "@/pages/design-cnstn";
|
||||||
|
import { parseTaskSeComCdFromApi } from "@/pages/design-cnstn/lib/design-cnstn-regist-schema";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: Promise<{
|
||||||
|
taskSeComCd?: string;
|
||||||
|
admdstCd?: string;
|
||||||
|
sido?: string;
|
||||||
|
gugun?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ searchParams }: PageProps) {
|
||||||
|
const params = await searchParams;
|
||||||
|
const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DesignRegistPage
|
||||||
|
defaults={{
|
||||||
|
taskSeComCd: taskSeComCd.length ? taskSeComCd : undefined,
|
||||||
|
admdstCd: params.admdstCd,
|
||||||
|
sido: params.sido,
|
||||||
|
gugun: params.gugun,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
import { FnrsViewPage } from "@/pages/fnrs-cnsltn";
|
import { FnrsDetailGate } from "@/pages/fnrs-cnsltn";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
@@ -6,5 +6,5 @@ interface PageProps {
|
|||||||
|
|
||||||
export default async function Page({ params }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
return <FnrsViewPage id={id} />;
|
return <FnrsDetailGate id={id} />;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { FnrsRegistPage } from "@/pages/fnrs-cnsltn";
|
||||||
|
import { parseTaskSeComCdFromApi } from "@/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: Promise<{
|
||||||
|
taskSeComCd?: string;
|
||||||
|
admdstCd?: string;
|
||||||
|
sido?: string;
|
||||||
|
gugun?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ searchParams }: PageProps) {
|
||||||
|
const params = await searchParams;
|
||||||
|
const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FnrsRegistPage
|
||||||
|
defaults={{
|
||||||
|
taskSeComCd: taskSeComCd.length ? taskSeComCd : undefined,
|
||||||
|
admdstCd: params.admdstCd,
|
||||||
|
sido: params.sido,
|
||||||
|
gugun: params.gugun,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { InstlViewPage } from "@/pages/instl-sncn";
|
import { InstlSncnDetailGate } from "@/pages/instl-sncn";
|
||||||
|
|
||||||
export default async function InstlSncnDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
export default async function InstlSncnDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
return <InstlViewPage id={id} />;
|
return <InstlSncnDetailGate id={id} />;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { InstlRegistPage } from "@/pages/instl-sncn";
|
||||||
|
|
||||||
|
interface InstlSncnRegistRouteProps {
|
||||||
|
searchParams: Promise<{
|
||||||
|
taskSeComCd?: string;
|
||||||
|
admdstCd?: string;
|
||||||
|
sido?: string;
|
||||||
|
gugun?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ searchParams }: InstlSncnRegistRouteProps) {
|
||||||
|
const params = await searchParams;
|
||||||
|
const taskSeComCd = params.taskSeComCd
|
||||||
|
? params.taskSeComCd.split(",").filter(Boolean)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InstlRegistPage
|
||||||
|
defaults={{
|
||||||
|
taskSeComCd,
|
||||||
|
admdstCd: params.admdstCd,
|
||||||
|
sido: params.sido,
|
||||||
|
gugun: params.gugun,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { MyPbadmsSprtListPage } from "@/pages/my-pbadms-sprt";
|
||||||
|
|
||||||
|
export default MyPbadmsSprtListPage;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { NtiHstryListPage } from "@/pages/nti-hstry";
|
||||||
|
|
||||||
|
export default NtiHstryListPage;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { SewerBscPlanEditPage } from "@/pages/sewer-bsc-plan";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
searchParams: Promise<{ chgCycl?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params, searchParams }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
const { chgCycl } = await searchParams;
|
||||||
|
const propChgCycl = chgCycl ? Number(chgCycl) : undefined;
|
||||||
|
return <SewerBscPlanEditPage id={id} propChgCycl={propChgCycl} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { SewerBscPlanDetailGate } from "@/pages/sewer-bsc-plan";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
searchParams: Promise<{ chgCycl?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params, searchParams }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
const { chgCycl } = await searchParams;
|
||||||
|
const propChgCycl = chgCycl ? Number(chgCycl) : undefined;
|
||||||
|
return <SewerBscPlanDetailGate id={id} propChgCycl={propChgCycl} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { SewerBscPlanListPage } from "@/pages/sewer-bsc-plan";
|
||||||
|
|
||||||
|
export default SewerBscPlanListPage;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { SewerBscPlanRegistPage } from "@/pages/sewer-bsc-plan";
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: Promise<{
|
||||||
|
taskSeComCd?: string;
|
||||||
|
taskSeCdNm?: string;
|
||||||
|
sewerBscPlanSeComCd?: string;
|
||||||
|
sewerBscPlanSeCdNm?: string;
|
||||||
|
admdstCd?: string;
|
||||||
|
sido?: string;
|
||||||
|
gugun?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ searchParams }: PageProps) {
|
||||||
|
const params = await searchParams;
|
||||||
|
|
||||||
|
return <SewerBscPlanRegistPage defaults={params} />;
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ import { Providers } from "@/app/providers";
|
|||||||
import "@/app/styles/globals.css";
|
import "@/app/styles/globals.css";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "행정지원시스템",
|
title: "디지털 행정지원 시스템",
|
||||||
description: "행정지원시스템",
|
description: "디지털 행정지원 시스템",
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RootLayoutProps {
|
interface RootLayoutProps {
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { FnrsRegistPage } from "@/pages/fnrs-cnsltn";
|
|
||||||
import { parseTaskSeComCdFromApi } from "@/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema";
|
|
||||||
|
|
||||||
interface PageProps {
|
|
||||||
searchParams: Promise<{ taskSeComCd?: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Page({ searchParams }: PageProps) {
|
|
||||||
const params = await searchParams;
|
|
||||||
const taskSeComCd = parseTaskSeComCdFromApi(params.taskSeComCd);
|
|
||||||
return <FnrsRegistPage taskSeComCd={taskSeComCd.length ? taskSeComCd : undefined} />;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { InstlRegistPage } from "@/pages/instl-sncn";
|
|
||||||
|
|
||||||
interface InstlSncnRegistRouteProps {
|
|
||||||
searchParams: Promise<{ taskSeComCd?: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Page({ searchParams }: InstlSncnRegistRouteProps) {
|
|
||||||
const params = await searchParams;
|
|
||||||
const taskSeComCd = params.taskSeComCd
|
|
||||||
? params.taskSeComCd.split(",").filter(Boolean)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return <InstlRegistPage taskSeComCd={taskSeComCd} />;
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { MainPage as default } from "@/pages/home";
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
import { COOKIE_DOMAIN, REFRESH_TOKEN } from "@/shared/config";
|
||||||
|
|
||||||
|
export async function POST() {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
cookieStore.delete({ name: REFRESH_TOKEN, domain: COOKIE_DOMAIN });
|
||||||
|
return NextResponse.json({ message: "Logged out" }, { status: 200 });
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
import "@/app/styles/globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "디지털 행정지원 시스템",
|
||||||
|
description: "페이지가 존재하지 않습니다. 관리자에게 문의하여 주십시오.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function GlobalNotFound() {
|
||||||
|
return (
|
||||||
|
<html lang="ko">
|
||||||
|
<body>
|
||||||
|
<main className="flex min-h-screen flex-col items-center justify-center bg-[#f0f4f8] px-4 py-16">
|
||||||
|
<div className="mb-16 text-center">
|
||||||
|
<h1 className="text-[3.2rem] font-bold leading-snug tracking-tight">
|
||||||
|
<span className="text-primary">디지털 행정지원 시스템</span>에 오신 것을 환영합니다.
|
||||||
|
</h1>
|
||||||
|
<p className="mt-6 text-[2rem] text-[#666]">오류가 발생하였습니다.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section className="card card-border flex w-full max-w-[72rem] flex-col items-center !rounded-2xl !py-16">
|
||||||
|
<Image
|
||||||
|
src="/images/magnifying-glass-with-warning.png"
|
||||||
|
alt=""
|
||||||
|
width={280}
|
||||||
|
height={280}
|
||||||
|
className="size-[28rem] object-contain"
|
||||||
|
/>
|
||||||
|
<p className="mt-4 text-[2.4rem] font-bold text-[#444]">페이지가 존재하지 않습니다.</p>
|
||||||
|
<p className="mt-2 text-[1.6rem] text-[#666]">관리자에게 문의하여 주십시오</p>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
# 재원협의(fnrs-cnsltn) vs 설치인가(instl-sncn) 차이 분석
|
|
||||||
|
|
||||||
| 항목 | 내용 |
|
|
||||||
| --------- | ----------------------------------------------------------------------------- |
|
|
||||||
| 작성일 | 2026-07-09 |
|
|
||||||
| 기준 구현 | `fnrs-cnsltn` (추가 요구사항 반영 완료) |
|
|
||||||
| 비교 대상 | `instl-sncn` |
|
|
||||||
| 목적 | 두 페이지의 동작·구조 차이를 정리하고, 설치인가 정렬 시 이식 대상을 명확히 함 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 요약
|
|
||||||
|
|
||||||
두 모듈은 **동일한 PBADMS(공공행정 지원) 업무 패턴**을 따르는 형제 페이지다.
|
|
||||||
|
|
||||||
공통 흐름:
|
|
||||||
|
|
||||||
```
|
|
||||||
목록 → TaskSe 선택 후 신청 → 저장(SV) → 신청완료(ST)
|
|
||||||
→ 환경청제출(LE) → 기술검토 → 검토결과 회신(EP)
|
|
||||||
→ 인가승인(TA) → 고시(TB)
|
|
||||||
```
|
|
||||||
|
|
||||||
| 구분 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------- | ----------------- | ---------------- |
|
|
||||||
| 업무명 | 재원협의 | 설치인가 |
|
|
||||||
| 문서번호 | `fnrsCnsltnDocNo` | `instlSncnDocNo` |
|
|
||||||
| docSeComCd | `FNRS` | `INSTL` |
|
|
||||||
| 라우트 | `/fnrs-cnsltn/*` | `/instl-sncn/*` |
|
|
||||||
| pages 파일 수 | 39 | 32 |
|
|
||||||
|
|
||||||
**결론:** `fnrs-cnsltn`이 추가 요구사항이 반영된 기준(reference) 구현이고, `instl-sncn`은 동일 골격이지만 보완요청·결재선 편집·등록/수정 폼 완성도 등에서 미반영·TODO·단순화 상태다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 파일/폴더 구조
|
|
||||||
|
|
||||||
### 2-1. fnrs-cnsltn에만 존재
|
|
||||||
|
|
||||||
| 경로 | 역할 |
|
|
||||||
| -------------------------------------------- | ------------------------- |
|
|
||||||
| `ui/sections/fnrs-shar-section.tsx` | 재원분담 조회 섹션 |
|
|
||||||
| `ui/sections/supplement-demand-section.tsx` | 보완요청 이력 섹션 |
|
|
||||||
| `ui/components/sortable-prgrs-stts-row.tsx` | 결재선 DnD 편집 행 |
|
|
||||||
| `ui/components/sortable-apln-picker-row.tsx` | 결재선 템플릿 피커 DnD 행 |
|
|
||||||
| `ui/components/aprvr-picker-dialog.tsx` | 결재자 선택 다이얼로그 |
|
|
||||||
| `ui/components/elapr-apln-picker-grid.tsx` | 결재선 편집 그리드 |
|
|
||||||
| `ui/components/elapr-tmplt-select-table.tsx` | 결재서식 선택 테이블 |
|
|
||||||
| `model/reviewer-option.ts` | 결재자 옵션 타입 |
|
|
||||||
|
|
||||||
### 2-2. instl-sncn에만 존재
|
|
||||||
|
|
||||||
| 경로 | 역할 | 비고 |
|
|
||||||
| -------------------------------------- | ------------------- | ------------------------------------------ |
|
|
||||||
| `ui/sections/history-info-section.tsx` | 등록/수정 이력 표시 | **어디에서도 import되지 않음 (dead code)** |
|
|
||||||
|
|
||||||
### 2-3. 공통 구조
|
|
||||||
|
|
||||||
```
|
|
||||||
index.ts
|
|
||||||
ui/
|
|
||||||
{list,view,form,regist,edit}-page.tsx
|
|
||||||
sections/ (basic-info, contact, scale, cost, checklist, attachments, tech-review)
|
|
||||||
components/ (floating-action-buttons, elapr-approval-dialog, elapr-tmplt-picker-dialog, checklist-task-panel)
|
|
||||||
lib/ button-config, save-payload-utils, form-data-mapper, regist-schema, checklist/review utils
|
|
||||||
model/ columns.tsx, view-types.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
App Router도 동일: `page.tsx`, `regist/page.tsx`, `[id]/page.tsx`, `[id]/edit/page.tsx`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 목록 페이지
|
|
||||||
|
|
||||||
### 3-1. 검색 필터
|
|
||||||
|
|
||||||
| 항목 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------------ | ----------------------------------------- | --------------------------------- |
|
|
||||||
| 시도/시군구 API | `@/entities/adstdstr` | 동일 |
|
|
||||||
| 지역 검색 파라미터 | **`admdstCd`** (시군구 우선, 없으면 시도) | **`sidoComCd` + `sggComCd`** 분리 |
|
|
||||||
| "전체" 옵션 | `ALL_SELECT_VALUE = "__ALL__"` SelectItem | 없음 (placeholder만) |
|
|
||||||
| 구분 CodeSelect | `upCd="INSTL"` ※ | `upCd="INSTL"` |
|
|
||||||
| 사업명 | 동일 | 동일 |
|
|
||||||
|
|
||||||
> ※ fnrs 목록의 구분 필터가 `INSTL` 코드를 사용 중이며, TaskSePickerDialog는 `codeUpCd="FNRS"`를 쓴다. **불일치 가능성**이 있어 확인이 필요하다.
|
|
||||||
|
|
||||||
### 3-2. 정렬·페이지네이션
|
|
||||||
|
|
||||||
| 항목 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------------------ | ---------------------- | ------------------------------------- |
|
|
||||||
| 기본 sort (list-page) | `fnrsCnsltnDocNo,desc` | `instlSncnDocNo,desc` |
|
|
||||||
| fallback sort (list.tsx) | `fnrsCnsltnDocNo,desc` | **`regDt,desc`** (list-page와 불일치) |
|
|
||||||
| 페이지 크기 | 10 | 10 |
|
|
||||||
| URL 동기화 | 없음 (`useState` 로컬) | 없음 |
|
|
||||||
|
|
||||||
### 3-3. 테이블 컬럼
|
|
||||||
|
|
||||||
`model/columns.tsx` 로직은 사실상 동일하다. DTO 타입명만 다르다.
|
|
||||||
|
|
||||||
컬럼: 시도, 시군구, 사업명, 업무구분, 담당자, 해당기관, 진행상황, 신청일
|
|
||||||
|
|
||||||
### 3-4. 행 클릭·신청
|
|
||||||
|
|
||||||
| 항목 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------ | ------------------------------------------------------------------ | ------------------ |
|
|
||||||
| 행 클릭 | **조건부 라우팅**: `SV`/`SL` + 등록자 본인 → `/edit`, 그 외 → view | **항상 view** |
|
|
||||||
| useUserStore | 사용 | 미사용 |
|
|
||||||
| 신청 버튼 | TaskSePickerDialog `codeUpCd="FNRS"` | `codeUpCd="INSTL"` |
|
|
||||||
|
|
||||||
```46:54:apps/as/src/pages/fnrs-cnsltn/ui/fnrs-list.tsx
|
|
||||||
const handleRowClick = (row: FnrsCnsltnMngResponseDto) => {
|
|
||||||
const canEdit =
|
|
||||||
(row.prgrsSttsComCd === "SV" || row.prgrsSttsComCd === "SL") &&
|
|
||||||
row.rgtr === userInfo?.userUnqId;
|
|
||||||
|
|
||||||
router.push(
|
|
||||||
canEdit ? `/fnrs-cnsltn/${row.fnrsCnsltnDocNo}/edit` : `/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 상세(View) 페이지
|
|
||||||
|
|
||||||
### 4-1. 섹션 구성
|
|
||||||
|
|
||||||
| 섹션 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------------------------- | ------------------------------------------ | -------------------------- |
|
|
||||||
| BasicInfo | 환경청구분, 확정예산사업번호, 예산신청연도 | **사업구분(`sncnSeCdNm`)** |
|
|
||||||
| ContactInfo | 동일 패턴 | 동일 |
|
|
||||||
| BusinessScale / Cost | 동일 | 동일 |
|
|
||||||
| **FnrsShar (재원분담)** | **있음** | **없음** (도메인 고유) |
|
|
||||||
| Checklist | 동일 | 동일 |
|
|
||||||
| Attachments | 동일 | 동일 |
|
|
||||||
| **SupplementDemand (보완요청)** | **있음** | **없음** |
|
|
||||||
| TechReviewOpinion | 동일 | 동일 |
|
|
||||||
| HistoryInfo | 없음 | 파일만 존재, view 미연결 |
|
|
||||||
| FloatingActionButtons | `docSeComCd="FNRS"` | `docSeComCd="INSTL"` |
|
|
||||||
|
|
||||||
### 4-2. 데이터 훅
|
|
||||||
|
|
||||||
| 항목 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| --------- | ---------------------------- | -------------------- |
|
|
||||||
| 상세 훅 | `useFnrsCnsltnMng` | `useInstlSncnDetail` |
|
|
||||||
| 에러 판정 | `isError \|\| !data?.detDto` | `isError \|\| !data` |
|
|
||||||
| 로딩 | Skeleton | Skeleton |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 등록/수정(Form) 페이지
|
|
||||||
|
|
||||||
### 5-1. 폼 완성도
|
|
||||||
|
|
||||||
| 항목 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------- | --------------------------------- | --------------- |
|
|
||||||
| 저장(SV) | O | O (SV 고정) |
|
|
||||||
| 신청완료(ST) | O (edit 모드) | **없음** |
|
|
||||||
| 삭제 | O (edit 모드, `useConfirm`) | **없음** |
|
|
||||||
| 재원분담 섹션 | `FnrsSharFormSection` + 잔액 검증 | 없음 |
|
|
||||||
| 저장 후 이동 | **`/edit` 유지** | **view로 이동** |
|
|
||||||
| 목록 버튼 | `/fnrs-cnsltn` 고정 | `router.back()` |
|
|
||||||
|
|
||||||
### 5-2. 기본정보 Form 필드
|
|
||||||
|
|
||||||
| 필드 | fnrs | instl |
|
|
||||||
| ----------------- | ---------------- | --------------------------------------- |
|
|
||||||
| taskSe 코드 | `FNRS` | `INSTL` |
|
|
||||||
| 사업구분 | 없음 | **`sncnSeComCd` (SNCNSE 라디오, 필수)** |
|
|
||||||
| BnsPicker 연동 | bizNm ~ bizCn | + **`bgtBizCd`, `bgtAplyYr`** |
|
|
||||||
| 재이용(RE) 조건부 | rtlztnFclt / Usg | 동일 |
|
|
||||||
|
|
||||||
### 5-3. Schema
|
|
||||||
|
|
||||||
| 항목 | fnrs | instl |
|
|
||||||
| ------------------- | ------------------- | -------------- |
|
|
||||||
| 관리 객체 | `fnrsCnsltnMng` | `instlSncnMng` |
|
|
||||||
| 예산 필드 | `cfmtnBgtBizNo` | `bgtBizCd` |
|
|
||||||
| 재원분담 | `fnrsSharList` 배열 | 없음 |
|
|
||||||
| sharAtchFileGroupId | 있음 | 없음 |
|
|
||||||
| privacyConsent | 있음 | 있음 |
|
|
||||||
|
|
||||||
### 5-4. Edit 페이지 래퍼
|
|
||||||
|
|
||||||
| 항목 | fnrs | instl |
|
|
||||||
| ------------------ | ------------------------------- | ------------------------------- |
|
|
||||||
| 로딩 Skeleton | 있음 | **없음** |
|
|
||||||
| 데이터 없음 메시지 | "상세 정보를 찾을 수 없습니다." | "수정 대상을 찾을 수 없습니다." |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. FloatingActionButtons / 결재 / 보완
|
|
||||||
|
|
||||||
### 6-1. FAB 동작 차이
|
|
||||||
|
|
||||||
공통: 역할×상태 기반 버튼(`button-config.ts`), checklist 진행률 검증, elapr pending approval 체크
|
|
||||||
|
|
||||||
| 기능 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| ------------------------------- | ------------------------------------------------------- | ------------------------------------------------------- |
|
|
||||||
| 보완완료 (`splmntCmptn`) | **`submitSupplement` API 연동** | **`// TODO` stub** |
|
|
||||||
| 보완요청 (`dmndSplmnt`) | **`buildSupplementReqPayload` + `submitReqSupplement`** | **`// TODO` stub** (문자열만 전달) |
|
|
||||||
| 검토완료 (`rvwCmptn`) | `techReViewSaveCd: "SV"` 후 **결재 다이얼로그 오픈** | `techReViewSaveCd: "CP"`, **다이얼로그 없이 즉시 완료** |
|
|
||||||
| 검토자지정 | `buildElaprPrgrsSttsListFromAplnRows(aplnRows)` | `buildElaprPrgrsSttsListFromApln(aplnPage.content)` |
|
|
||||||
| atrzAprv / atrzRply / grndsChck | TODO handler | TODO handler |
|
|
||||||
|
|
||||||
```576:617:apps/as/src/pages/instl-sncn/ui/components/floating-action-buttons.tsx
|
|
||||||
async function handleSplmntCmptn(context: ActionHandlerContext): Promise<void> {
|
|
||||||
// TODO: 보완완료 API 호출
|
|
||||||
context.onSuccess?.();
|
|
||||||
}
|
|
||||||
// ...
|
|
||||||
async function handleDmndSplmnt(context: ActionHandlerContext): Promise<void> {
|
|
||||||
// TODO: 보완요청 API 호출
|
|
||||||
context.onSuccess?.();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6-2. ElaprApprovalDialog (가장 큰 UX 격차)
|
|
||||||
|
|
||||||
| 항목 | fnrs (419줄) | instl (231줄) |
|
|
||||||
| ---------------- | ------------------------------------------------------ | -------------------------------- |
|
|
||||||
| 결재선 표시 | DnD sortable 편집 테이블 | **읽기 전용** 테이블 |
|
|
||||||
| 결재선 추가/저장 | **지원** (`usePrgrsSttsRows`, `useSaveElaprPrgrsStts`) | 없음 |
|
|
||||||
| 결재자 변경 | **AprvrPickerDialog** | 없음 |
|
|
||||||
| 전결 체크박스 | **있음** (`finalApprovalYn`) | 없음 |
|
|
||||||
| 반려 사유 | API에 `rjctRsn` 전달 | UI는 있으나 **payload에 미전달** |
|
|
||||||
| 저장 전 검증 | `hasUnsavedPrgrsSttsChanges` | 없음 |
|
|
||||||
|
|
||||||
### 6-3. ElaprTmpltPickerDialog
|
|
||||||
|
|
||||||
| 항목 | fnrs | instl |
|
|
||||||
| --------------- | -------------------------------------------------------------------- | ----------------------- |
|
|
||||||
| UI | 2-pane: 서식 테이블 + **결재선 편집 그리드** | 단일 테이블 + 선택 버튼 |
|
|
||||||
| docSeComCd 필터 | `FNRS` | `INSTL` |
|
|
||||||
| onConfirm | `{ tmplt, aplnRows }` 편집 결과 | `onSelect(tmplt)`만 |
|
|
||||||
| 검증 | `validateAllRowsForSave`, `validateAtrzOrder`, `validateUniqueSeqNo` | 없음 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Entity / API / Hook
|
|
||||||
|
|
||||||
### 7-1. Service 메서드
|
|
||||||
|
|
||||||
| 메서드 | fnrs | instl |
|
|
||||||
| ----------------------- | --------------------------- | --------- |
|
|
||||||
| 목록 | `findPage` | `getPage` |
|
|
||||||
| 상세 | `findById` | `getById` |
|
|
||||||
| saveApply | O | O |
|
|
||||||
| techReview | O | O |
|
|
||||||
| changePrgrsSttsComCd | O | O |
|
|
||||||
| remove | O | O |
|
|
||||||
| **submitSupplement** | **O** (`/submitsupplement`) | **X** |
|
|
||||||
| **submitReqSupplement** | **O** (`/reqsupplement`) | **X** |
|
|
||||||
|
|
||||||
### 7-2. 상세 DTO 확장
|
|
||||||
|
|
||||||
**fnrs 추가 필드**
|
|
||||||
|
|
||||||
- `fnrsSharList`, `sharAtchFileList`
|
|
||||||
- `splmntDmndList` (typed)
|
|
||||||
|
|
||||||
**instl 추가 필드**
|
|
||||||
|
|
||||||
- `sncnSeComCd` / `sncnSeCdNm` (인가구분)
|
|
||||||
- `bgtBizCd` (예산사업코드)
|
|
||||||
- `rtlztnFcltSeComNm`, `rtlztnUsgSeComNm`
|
|
||||||
- `splmntDmndList` 타입은 base에 존재하나 view/API 미연결
|
|
||||||
- `instlSncnRegionService` (시도/시군구 — 목록은 adstdstr 사용, 미활용)
|
|
||||||
|
|
||||||
### 7-3. save-payload-utils
|
|
||||||
|
|
||||||
| 함수 | fnrs | instl |
|
|
||||||
| -------------------------------- | ---------------- | --------- |
|
|
||||||
| buildSavePayload | + `fnrsSharList` | shar 없음 |
|
|
||||||
| buildTechReviewReplyPayload | O | O |
|
|
||||||
| **buildSupplementReqPayload** | **O** | **X** |
|
|
||||||
| **buildSupplementSubmitPayload** | **O** | **X** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 비즈니스 도메인 차이
|
|
||||||
|
|
||||||
| 영역 | fnrs-cnsltn | instl-sncn |
|
|
||||||
| -------------- | ----------------------------------------- | ------------------------ |
|
|
||||||
| 업무 코드 | `FNRS` | `INSTL` |
|
|
||||||
| TaskSe | FNRS 하위 | INSTL 하위 |
|
|
||||||
| 고시 버튼 라벨 | "재원협의 고시" | "설치인가 고시" |
|
|
||||||
| 재원분담 | **핵심 도메인** (`fnrs-shar-se`) | 해당 없음 |
|
|
||||||
| 인가구분 | 없음 | **`SNCNSE` 코드 (필수)** |
|
|
||||||
| 예산 식별 | `cfmtnBgtBizNo` | `bgtBizCd` |
|
|
||||||
| 진행상태 코드 | SV, ST, LE, ER, TE, EP, TA, TB, SL, TP 등 | **동일 세트 공유** |
|
|
||||||
| 역할 매핑 | ROLE_USER05/04/03/01 | 동일 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. fnrs-cnsltn에만 있는 기능 (추가 요구사항 후보)
|
|
||||||
|
|
||||||
instl-sncn 정렬 시 **우선 이식 대상**:
|
|
||||||
|
|
||||||
1. 목록 행 클릭 조건부 edit 라우팅 (`SV`/`SL` + 등록자)
|
|
||||||
2. 보완요청 전체 플로우 — view 섹션, payload builder, API, FAB handler
|
|
||||||
3. 보완완료 API 연동 (`submitSupplement`)
|
|
||||||
4. 등록/수정 폼 완성 — 신청완료, 삭제, 저장 후 edit 유지
|
|
||||||
5. 결재선 DnD 편집형 `ElaprApprovalDialog` (+ aprvr-picker, sortable rows)
|
|
||||||
6. 결재선 편집형 `ElaprTmpltPickerDialog` (+ apln grid, validation)
|
|
||||||
7. 검토완료 → 결재 다이얼로그 연계 흐름
|
|
||||||
8. 검색 필터 "전체" 옵션 및 `admdstCd` 통합 방식 (백엔드 API에 맞게)
|
|
||||||
9. Edit 페이지 로딩 Skeleton
|
|
||||||
10. entity에 supplement API/hooks 추가
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. instl-sncn에만 있는 기능 (유지 필요)
|
|
||||||
|
|
||||||
1. **`sncnSeComCd` (사업구분/인가구분)** — 도메인 고유
|
|
||||||
2. **`bgtBizCd` / `bgtAplyYr`** BnsPicker 연동
|
|
||||||
3. **`history-info-section.tsx`** — 구현만 있고 view 미연결 (활성화 또는 삭제 결정 필요)
|
|
||||||
4. entity **`instlSncnRegionService`** — 목록은 adstdstr 사용 중이라 중복/레거시 가능
|
|
||||||
5. **`INSTL_SNCN_REGISTER_SELECTION_KEY`** sessionStorage 키
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. 동일·거의 동일 패턴
|
|
||||||
|
|
||||||
| 영역 | 동일도 |
|
|
||||||
| ----------------------------------------------------- | --------------------- |
|
|
||||||
| App Router 4-route 구조 | 100% |
|
|
||||||
| `button-config.ts` 역할×상태 매트릭스 | ~99% (액션명만 다름) |
|
|
||||||
| `columns.tsx` 진행상황 Badge | ~100% |
|
|
||||||
| checklist / contact / scale / cost / attachments 섹션 | ~95% (import 경로만) |
|
|
||||||
| review-step-utils 로직 | ~100% (상수명만 다름) |
|
|
||||||
| TaskSePickerDialog 신청 플로우 | 100% |
|
|
||||||
| FAB 전체 구조·대부분 handler | ~85% |
|
|
||||||
| checklist-form-state, business-cost-utils | 거의 동일 |
|
|
||||||
| shared widgets (`@/widgets/pbadms-sprt`) | 동일 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 12. instl-sncn 정렬 권장 우선순위
|
|
||||||
|
|
||||||
| 우선순위 | 항목 | 근거 |
|
|
||||||
| -------- | ------------------------------------------------------- | ---------------------------- |
|
|
||||||
| P0 | 보완요청/보완완료 API + UI | fnrs 완전 구현 vs instl TODO |
|
|
||||||
| P0 | ElaprApprovalDialog 고도화 | 결재선 편집·전결·반려사유 |
|
|
||||||
| P0 | ElaprTmpltPickerDialog 2-pane | 검토자지정 품질 |
|
|
||||||
| P1 | Form 신청완료/삭제 + 저장 후 edit 유지 | 등록 UX parity |
|
|
||||||
| P1 | 목록 행 클릭 edit 라우팅 | fnrs 추가 요구사항 |
|
|
||||||
| P1 | 검토완료 → 결재 다이얼로그 연계 | 흐름 통일 |
|
|
||||||
| P2 | sort fallback 불일치 수정 (`regDt` vs `instlSncnDocNo`) | instl 내부 버그성 |
|
|
||||||
| P2 | history-info-section 연결 또는 삭제 | dead code 정리 |
|
|
||||||
| P3 | URL search params 동기화 | 양쪽 모두 미구현 (공통 개선) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 13. 참고 코드 위치
|
|
||||||
|
|
||||||
| 주제 | 경로 |
|
|
||||||
| ---------------------------- | ------------------------------------------------------------ |
|
|
||||||
| 목록 edit 라우팅 (fnrs) | `pages/fnrs-cnsltn/ui/fnrs-list.tsx` |
|
|
||||||
| 목록 항상 view (instl) | `pages/instl-sncn/ui/instl-list.tsx` |
|
|
||||||
| view 추가 섹션 (fnrs) | `pages/fnrs-cnsltn/ui/fnrs-view-page.tsx` |
|
|
||||||
| FAB 보완 TODO (instl) | `pages/instl-sncn/ui/components/floating-action-buttons.tsx` |
|
|
||||||
| 결재 다이얼로그 (fnrs) | `pages/fnrs-cnsltn/ui/components/elapr-approval-dialog.tsx` |
|
|
||||||
| 결재 다이얼로그 (instl) | `pages/instl-sncn/ui/components/elapr-approval-dialog.tsx` |
|
|
||||||
| 폼 완성도 (fnrs) | `pages/fnrs-cnsltn/ui/fnrs-form-page.tsx` |
|
|
||||||
| 폼 단순화 (instl) | `pages/instl-sncn/ui/instl-form-page.tsx` |
|
|
||||||
| supplement API (fnrs entity) | `entities/fnrs-cnsltn/api/fnrs-cnsltn-service.ts` |
|
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
# 재원협의·실무협의 프론트엔드 개발 가이드
|
||||||
|
|
||||||
|
> **대상 앱**: `frontend/apps/as`
|
||||||
|
> **작성일**: 2026-08-27
|
||||||
|
> **개정일**: 2026-09-07
|
||||||
|
> **버전**: 2.0
|
||||||
|
> **Process**: PR-AS-003 (재원협의 관리 — 실무협의 및 AI 검증 통합)
|
||||||
|
>
|
||||||
|
> **SoT (2026-09-03 완결)**:
|
||||||
|
>
|
||||||
|
> - 프로세스·상태·버튼: `03.재원협의_실무협의_프로세스_통합_v완결.md`
|
||||||
|
> - REST 설계: `05.재원협의_상태별_endpoint_설계.md`
|
||||||
|
> - 공통코드: `FNRSSTTS` (`SV, ST, LE, FV, ER, SL, AC, TE, TP, RM, RB, RC, RA, AV, EV, RK, RR, EP, TA`)
|
||||||
|
>
|
||||||
|
> **백엔드 서비스**:
|
||||||
|
>
|
||||||
|
> - `as-service` — `/api/v1/fnrs-cnsltn-mng`, `/api/v1/pwrk-cnsltn-mng`, `/api/v1/pwrk-cnsltn-doc-crt-rslt`, `/api/v1/dcrag-to`
|
||||||
|
> - `as-elapr-service` — `/api/v1/elapr-dmnd`, `/api/v1/elapr-prgrs-stts`, `/api/v1/elapr-apln`
|
||||||
|
>
|
||||||
|
> **현재 프론트 상태**: 재원협의(`fnrs-cnsltn`)·전자결재(`elapr`)·DC RAG TO(`dcrag-to`)는 존재한다. 실무협의(`pwrk-cnsltn-`\*) 엔티티·화면은 **미구현**. 버튼 매트릭스·상태코드는 **v2.1 잔존**이며 v3.0(`FNRSSTTS`)과 불일치한다. [9절](#9-현재-코드-vs-to-be-갭) 참고.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 문서 목적
|
||||||
|
|
||||||
|
재원협의 4대 주체(기초·광역·환경청·공단)의 **상태 전이·버튼·API**를 `apps/as`에 구현할 때 쓰는 프론트엔드 가이드이다.
|
||||||
|
|
||||||
|
v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기**만 다뤘다. v2.0은 2026-09-03 완결 프로세스 기준으로 **신청부터 최종종결(**`TA`**)까지**, 실무협의·AI 3파이프라인·결재선·보완 루프를 포함한다.
|
||||||
|
|
||||||
|
상세 표·시퀀스·DTO는 본문에 넣지 않고 레퍼런스로 분리한다. 구현 중에는 본문의 규칙만 보고, 표가 필요하면 해당 파일을 연다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 문서 맵
|
||||||
|
|
||||||
|
| 파일 | 내용 | 언제 보는가 |
|
||||||
|
| -------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------- |
|
||||||
|
| [development-guide.md](./development-guide.md) (본 문서) | 도메인, 핵심 규칙, 화면 흐름, 갭, 구현 순서 | 일상 구현 |
|
||||||
|
| [ref-process-status.md](./ref-process-status.md) | 4-Lane 시퀀스, Activity, `FNRSSTTS`, 보완 3루프, AI 타임아웃, 결재선 복사 | 상태 전이·폴링 UX |
|
||||||
|
| [ref-screen-buttons.md](./ref-screen-buttons.md) | 38행 화면·역할·버튼 매트릭스, 버튼 가시성 | `button-config.ts` 개편 |
|
||||||
|
| [ref-endpoint.md](./ref-endpoint.md) | 버튼→API 총괄, 단계별 입출력, FE 호출 금지 API, Gap Analysis | 엔티티·service 매핑 |
|
||||||
|
| [ref-frontend-entities.md](./ref-frontend-entities.md) | ENDPOINT, 슬라이스, Service 시그니처, 폴링, 웹기안기, DTO | 엔티티 레이어 구현 |
|
||||||
|
| [ref-webhwp-api.md](./ref-webhwp-api.md) | 웹한글기안기 v3 `HwpCtrl` Properties/Methods, Open/SaveAs | 기안기 위젯 연동 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 도메인 개요
|
||||||
|
|
||||||
|
실무협의서(PWRK)는 재원협의서(FNRS)의 **하위 문서**이다. 재원협의 1건당 실무협의서는 **1건만** 생성된다.
|
||||||
|
|
||||||
|
| 문서 | 번호 예시 | 필드 |
|
||||||
|
| -------- | ----------------- | ----------------------------- |
|
||||||
|
| 재원협의 | `F-20260824-0001` | `fnrsCnsltnDocNo` / `upDocNo` |
|
||||||
|
| 실무협의 | `P-20260827-0001` | `pwrkCnsltnDocNo` |
|
||||||
|
|
||||||
|
진행상태는 **재원협의 마스터** `tb_as_fnrs_cnsltn_mng.prgrs_stts_com_cd` (`FNRSSTTS`)가 SSOT이다. 실무협의 문서의 `prgrsSttsComCd`는 연계 표시용이며, 화면 버튼은 재원협의 상태로 분기한다.
|
||||||
|
|
||||||
|
### 3-1. 4대 주체
|
||||||
|
|
||||||
|
| 역할 | 코드 | 화면 소유 | 핵심 구간 |
|
||||||
|
| --------------- | ------------- | -------------- | ------------------------------------ |
|
||||||
|
| 기초자치단체 | `ROLE_USER05` | 작성/수정/상세 | 신청, 3종 보완, 조치결과, 최종확인 |
|
||||||
|
| 광역자치단체 | `ROLE_USER04` | 상세 | 경유 제출, 결과 전달 |
|
||||||
|
| 유역·지방환경청 | `ROLE_USER03` | 상세 | 의뢰/자체종결, 1·2차 회신 |
|
||||||
|
| 한국환경공단 | `ROLE_USER01` | 상세 + 결재선 | 파일검증, 검토자지정, 1·2차 기술검토 |
|
||||||
|
|
||||||
|
### 3-2. 핵심 식별자
|
||||||
|
|
||||||
|
| 필드 | 의미 | 비고 |
|
||||||
|
| ----------------- | ---------------- | ----------------------------------------------------------- |
|
||||||
|
| `fnrsCnsltnDocNo` | 재원협의문서번호 | 상태 전이·목록 PK |
|
||||||
|
| `pwrkCnsltnDocNo` | 실무협의문서번호 | `P-YYYYMMDD-NNNN`. 검토자지정(`ER→AC`) 시 발급 |
|
||||||
|
| `upDocNo` | 상위문서번호 | 재원협의 번호. `upDocSeComCd = "FNRS"` |
|
||||||
|
| `crtDmndUuid` | 생성요청 UUID | 초안 폴링·웹기안기·다운로드 키 |
|
||||||
|
| `verSn` | 버전일련번호 | 복합 PK `(crtDmndUuid, verSn)`. 저장 시 서버가 `+1` |
|
||||||
|
| `orgnlOtptDocCn` | HWP 바이너리 | 목록/상세 응답은 항상 `null`. 다운로드·`webhwp/open`만 사용 |
|
||||||
|
| `elaprCycl` | 결재 차수 | 1차 `TE`, 2차 `RK`. 금액검증 완료 후 1차 결재선 복사 |
|
||||||
|
|
||||||
|
### 3-3. IIPBS 약어
|
||||||
|
|
||||||
|
| 한글 | 약어 | 필드 예 |
|
||||||
|
| ---- | ------------ | --------------------------- |
|
||||||
|
| 재원 | FNRS | `fnrsCnsltnDocNo` |
|
||||||
|
| 실무 | PWRK | `pwrkCnsltnDocNo` |
|
||||||
|
| 협의 | CNSLTN | `fnrs-cnsltn-mng` |
|
||||||
|
| 생성 | CRT | `crtDmndUuid` |
|
||||||
|
| 요청 | DMND | `crtDmndUuid`, `elapr-dmnd` |
|
||||||
|
| 검증 | VRFC | `vrfcSttsComCd` |
|
||||||
|
| 보완 | SPLMNT | `splmntDmndList` |
|
||||||
|
| 결재 | ELAPR / ATRZ | `elaprCycl`, `atrzSeComCd` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 상태코드 한눈에 보기
|
||||||
|
|
||||||
|
상위코드 `FNRSSTTS`. v1.0의 `PWRKSTTS`(`DV`, `AR`, `TR` 등)는 **폐기**한다.
|
||||||
|
|
||||||
|
정방향:
|
||||||
|
|
||||||
|
```
|
||||||
|
SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → EV → RK → RR → EP → TA
|
||||||
|
```
|
||||||
|
|
||||||
|
자체종결: `LE → EP → TA`
|
||||||
|
보완: `ER|TE|RK → SL` 후 직전 상태로 복귀.
|
||||||
|
|
||||||
|
| 코드 | 명칭 | FE에서 할 일 |
|
||||||
|
| ---- | ---------------------- | ------------------------------------------------- |
|
||||||
|
| `SV` | 저장 | 기초 작성화면 저장/신청/삭제 |
|
||||||
|
| `ST` | 신청완료 | 광역 환경청제출 |
|
||||||
|
| `LE` | 환경청 검토 | 자체종결(`EP`) 또는 기술검토 의뢰(`FV`) |
|
||||||
|
| `FV` | 파일검증 | AI 진행 표시. **30분** 후 `ER` Fallback |
|
||||||
|
| `ER` | 기술검토 의뢰 | 공단 접수자: 파일보완 / 수동파일검증 / 검토자지정 |
|
||||||
|
| `SL` | 보완요청 | 기초 수정화면. 복귀 대상은 이력의 직전 상태 |
|
||||||
|
| `AC` | 초안생성 | AI 진행 표시. **30분** 후 `TE` Fallback |
|
||||||
|
| `TE` | 기술검토 | 웹기안기·검토완료·보완. 결재선 1차 |
|
||||||
|
| `TP` | 기술검토 회신 | 환경청 → 광역 `RM` |
|
||||||
|
| `RM` | 재원협의결과회신 | 광역 → 기초 `RB` |
|
||||||
|
| `RB` | 지자체회신 | 기초 실무협의서 관리 / 조치완료 |
|
||||||
|
| `RC` | 조치결과서 제출 | 광역 → 환경청 `RA` |
|
||||||
|
| `RA` | 조치결과서제출(환경청) | 환경청 2차 의뢰 `AV` |
|
||||||
|
| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `EV` Fallback |
|
||||||
|
| `EV` | 조치결과검토의뢰 | 공단 접수자: 2차 결재선 검토자지정 → `RK` |
|
||||||
|
| `RK` | 조치결과서검토 | 웹기안기·금액검증 재실행·보완. 결재선 2차 |
|
||||||
|
| `RR` | 조치결과서검토 회신 | 환경청 → 광역 `EP` |
|
||||||
|
| `EP` | 검토결과 회신 | 광역 → 기초 `TA` |
|
||||||
|
| `TA` | 검토결과 회신(종료) | 기초 조회만. **고시(**`TB`**) 아님** |
|
||||||
|
|
||||||
|
코드 의미·구버전 대조·보완 루프·AI Fallback은 [ref-process-status.md](./ref-process-status.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 프론트가 지켜야 할 핵심 규칙
|
||||||
|
|
||||||
|
1. **상태 SSOT는** `FNRSSTTS`. 버튼 가시성은 `(dtlInstCd, prgrsSttsComCd, atrzSeComCd)`로만 계산한다. 공단은 결재선 구분(`ATRZ01` 접수자, `ATRZ02` 검토자, `ATRZ03` 승인자, `ATRZ99` 최종승인자)을 추가로 본다.
|
||||||
|
2. **AI 콜백 API는 화면에서 호출하지 않는다.** `POST /dcrag/documents/verify-result`, `POST /dcrag/draft/complete`, `POST /dcrag/cost/verify-result`는 서버 전용이다.
|
||||||
|
3. **초안생성 1차 트리거는 검토자지정이다.** `POST /elapr-dmnd` (`ER → AC`). 예전처럼 `POST /pwrk-cnsltn-mng`로 초안을 시작하지 않는다.
|
||||||
|
4. **기술검토 의뢰는** `LE → FV` **전용 API** `POST /fnrs-cnsltn-mng/{docNo}/request-examine`. 현재 코드의 `updatePrgrsStts(ER)` 직행은 폐기.
|
||||||
|
5. **수동 AI 재실행은 상태를 바꾸지 않는다.**
|
||||||
|
|
||||||
|
- `ER` 파일검증 → `POST /dcrag-to/documents/transfer`
|
||||||
|
- `TE` 초안생성 → `POST /dcrag-to/documents/draft/generate` (타임아웃·실패 시에만)
|
||||||
|
- `RK` 금액검증 → `POST /dcrag-to/documents/cost/verify`
|
||||||
|
|
||||||
|
6. **진행상태만 바꾸는 버튼은** `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts`**를 재사용한다.** (`ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`)
|
||||||
|
7. **보완요청/보완완료는 공통 API** `reqsupplement` / `submitsupplement`. 복귀 상태(`ER`/`TE`/`RK`)는 서버가 이력에서 결정한다. 프론트는 직전 상태를 추측해 보내지 않는다.
|
||||||
|
8. **결재 승인/회신은** `PUT /elapr-prgrs-stts/{id}/process` **하나**. 1차 최종승인자 → `TP`, 2차 → `RR`은 서버가 Feign으로 처리한다.
|
||||||
|
9. **2차 결재선은 EV에서 수동 검토자지정**. `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선 자동 copy-line은 사용하지 않는다.
|
||||||
|
10. **웹기안기는 `webhwp/open` 응답을 axios `blob`으로 조회한 뒤 `File`로 감싸 `HwpCtrl.Open`에 전달한다.** `blob:` Object URL과 `orgnlOtptDocCn`은 쓰지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 화면 데이터 흐름
|
||||||
|
|
||||||
|
재원협의 **목록 → 상세/작성**이 본체다. 실무협의 목록은 공단 검토 보조 화면이다.
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
CALL fnrsCnsltnMngService.findPage(...)
|
||||||
|
ON 행 CLICK → /fnrs-cnsltn/{fnrsCnsltnDocNo}
|
||||||
|
|
||||||
|
CALL findById(fnrsCnsltnDocNo)
|
||||||
|
COMPUTE buttons = getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList)
|
||||||
|
|
||||||
|
// ----- 기초 ROLE_USER05 -----
|
||||||
|
ON [저장] (빈값/SV) → saveApply(prgrsSttsComCd: SV)
|
||||||
|
ON [신청완료] (SV) → saveApply(prgrsSttsComCd: ST)
|
||||||
|
ON [삭제] (SV) → remove
|
||||||
|
ON [파일보완완료] (SL←ER) → submitsupplement
|
||||||
|
ON [보완완료] (SL←TE|RK) → submitsupplement
|
||||||
|
ON [실무협의서 관리] (RB) → GET webhwp/open (blob) → HwpCtrl.Open(File)
|
||||||
|
ON [조치완료] (RB) → submit-actn-result { isSubmit: true }
|
||||||
|
ON [결과회신] (TA) → API 없음 (조회)
|
||||||
|
|
||||||
|
// ----- 광역 ROLE_USER04 -----
|
||||||
|
ON [환경청제출] (ST) → updatePrgrsStts(LE)
|
||||||
|
ON [지자체회신] (RM) → updatePrgrsStts(RB)
|
||||||
|
ON [조치결과서제출] (RC) → updatePrgrsStts(RA)
|
||||||
|
ON [결과회신] (EP) → updatePrgrsStts(TA)
|
||||||
|
|
||||||
|
// ----- 환경청 ROLE_USER03 -----
|
||||||
|
ON [결과회신] (LE 자체종결) → tech-review → EP
|
||||||
|
ON [기술검토 의뢰] (LE) → request-examine → FV → START POLL FV
|
||||||
|
ON [재원협의 결과 회신] (TP) → tech-review → RM
|
||||||
|
ON [실무협의서 관리] (RA) → GET webhwp/open (blob) → HwpCtrl.Open(File) (저장 불가)
|
||||||
|
ON [조치결과 검토의뢰] (RA) → request-actn-review → AV → START POLL AV
|
||||||
|
ON [결과회신] (RR) → updatePrgrsStts(EP)
|
||||||
|
|
||||||
|
// ----- 공단 ROLE_USER01 -----
|
||||||
|
ON [파일보완] (ER, ATRZ01) → GET file-vrfc-results → 팝업 → reqsupplement
|
||||||
|
ON [파일검증] (ER, ATRZ01) → dcrag-to transfer (상태 유지)
|
||||||
|
ON [검토자지정] (ER, ATRZ01) → POST elapr-dmnd (docPrgrsSttsComCd: AC) → START POLL AC
|
||||||
|
ON [검토자지정] (EV, ATRZ01) → POST elapr-dmnd (elaprCycl: 2, docPrgrsSttsComCd: RK)
|
||||||
|
ON [실무협의서관리] (TE|RK) → GET webhwp/open (blob) → HwpCtrl.Open(File)
|
||||||
|
ON [실무협의서생성] (TE, 실패/타임아웃) → dcrag-to draft/generate
|
||||||
|
ON [검토결과조회] (TE) → pwrk-cnsltn-mng findById | findByUpDocNo
|
||||||
|
ON [저장] (TE|RK) → pwrk-cnsltn-mng save-apply
|
||||||
|
ON [검토완료] → elapr-prgrs-stts process (상신)
|
||||||
|
ON [보완요청] (TE|RK) → 보완 팝업 → reqsupplement
|
||||||
|
ON [금액검증] (RK) → dcrag-to cost/verify
|
||||||
|
ON [결재(승인)] → process (중간)
|
||||||
|
ON [결재(회신)] → process (최종 → TP 또는 RR)
|
||||||
|
|
||||||
|
// ----- AI 진행 중 (FV / AC / AV) -----
|
||||||
|
POLL 상태 또는 draft status. 30분 안내. 종료 후 상세 invalidate
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
38행 버튼 표는 [ref-screen-buttons.md](./ref-screen-buttons.md). API 상세는 [ref-endpoint.md](./ref-endpoint.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 버튼 → API 축약
|
||||||
|
|
||||||
|
프론트가 치는 것만. 콜백·스케줄러는 제외.
|
||||||
|
|
||||||
|
| 주체 | 버튼 | From → To | Method & URI |
|
||||||
|
| --------- | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------- |
|
||||||
|
| 기초 | 저장 / 신청완료 | →`SV` / `SV→ST` | `POST /fnrs-cnsltn-mng/save-apply` |
|
||||||
|
| 기초 | 삭제 | `SV` | `DELETE /fnrs-cnsltn-mng/{docNo}` |
|
||||||
|
| 광역 | 환경청제출 | `ST→LE` | `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` |
|
||||||
|
| 환경청 | 결과회신(자체종결) | `LE→EP` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
|
||||||
|
| 환경청 | 기술검토 의뢰 | `LE→FV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-examine` |
|
||||||
|
| 공단 접수 | 파일보완 팝업 | `ER` | `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results` |
|
||||||
|
| 공단 접수 | 파일보완요청 | `ER→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
|
||||||
|
| 공단 접수 | 파일검증 재실행 | `ER` 유지 | `POST /dcrag-to/documents/transfer` |
|
||||||
|
| 공단 접수 | 검토자지정 | `ER→AC` | `POST /elapr-dmnd` (`elaprCycl: 1`) |
|
||||||
|
| 공단 접수 | 검토자지정 | `EV→RK` | `POST /elapr-dmnd` (`elaprCycl: 2`) |
|
||||||
|
| 공단 검토 | 실무협의서관리 | `TE`/`RK` | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||||
|
| 공단 검토 | 실무협의서생성 | `TE` 유지 | `POST /dcrag-to/documents/draft/generate` |
|
||||||
|
| 공단 검토 | 저장 | `TE`/`RK` | `POST /pwrk-cnsltn-mng/save-apply` |
|
||||||
|
| 공단 | 검토완료 / 결재(승인·회신) | `TE` 유지 / `TE→TP` / `RK→RR` | `PUT /elapr-prgrs-stts/{id}/process` |
|
||||||
|
| 공단 검토 | 보완요청 | `TE→SL` / `RK→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` |
|
||||||
|
| 기초 | 파일보완완료 / 보완완료 | `SL→ER` / `TE` / `RK` | `POST /fnrs-cnsltn-mng/{docNo}/submitsupplement` |
|
||||||
|
| 환경청 | 재원협의 결과 회신 | `TP→RM` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` |
|
||||||
|
| 광역 | 지자체회신 | `RM→RB` | `updatePrgrsStts` |
|
||||||
|
| 기초 | 실무협의서 관리 | `RB` 유지 | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||||
|
| 기초 | 조치완료 | `RB→RA` | **신규** `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit: true`) |
|
||||||
|
| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` |
|
||||||
|
| 환경청 | 실무협의서 관리 | `RA` 유지 (조회) | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` |
|
||||||
|
| 환경청 | 조치결과 검토의뢰 | `RA→AV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` |
|
||||||
|
| 공단 검토 | 금액검증 재실행 | `RK` 유지 | `POST /dcrag-to/documents/cost/verify` |
|
||||||
|
| 환경청 | 결과회신 | `RR→EP` | `updatePrgrsStts` |
|
||||||
|
| 광역 | 결과회신 | `EP→TA` | `updatePrgrsStts` |
|
||||||
|
| 웹기안기 | 저장 | 버전 +1 | `POST /pwrk-cnsltn-doc-crt-rslt` multipart |
|
||||||
|
| 공통 | 다운로드 | — | `GET /pwrk-cnsltn-doc-crt-rslt/{uuid}/download` |
|
||||||
|
|
||||||
|
신규 4개: `request-examine`, `submit-actn-result`, `request-actn-review`, EV 2차 `elapr-dmnd`.
|
||||||
|
|
||||||
|
---
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
# 레퍼런스 — 상태별 Endpoint
|
||||||
|
|
||||||
|
> 상위: [개발가이드.md](./개발가이드.md)
|
||||||
|
> SoT: `05.재원협의_상태별_endpoint_설계.md` (v1.0, 2026-09-03)
|
||||||
|
> Gateway: `/as-service`, `/as-elapr-service`
|
||||||
|
> Auth: `Authorization`, `X-Site-Id`, `token-id` (기존 `client` 인터셉터)
|
||||||
|
> 날짜: `yyyy-MM-dd HH:mm:ss`
|
||||||
|
|
||||||
|
프론트가 치는 API만 상세히 적는다. AI 콜백·스케줄러·Feign은 [§6 호출 금지](#6-프론트-호출-금지).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 총괄 매트릭스 (38 + WebHWP)
|
||||||
|
|
||||||
|
| 순번 | 버튼 | From → To | 서비스 | Method & URI | 구분 |
|
||||||
|
| :---: | --- | --- | --- | --- | --- |
|
||||||
|
| 1 | 저장 | →`SV` | as | `POST /api/v1/fnrs-cnsltn-mng/save-apply` | 기존 |
|
||||||
|
| 2 | 신청완료 | `SV→ST` | as | 동일 (`prgrsSttsComCd=ST`) | 기존 |
|
||||||
|
| 3 | 삭제 | `SV` | as | `DELETE /api/v1/fnrs-cnsltn-mng/{docNo}` | 기존 |
|
||||||
|
| 4 | 환경청제출 | `ST→LE` | as | `POST /api/v1/fnrs-cnsltn-mng/doc/updatePrgrsStts` | 기존 |
|
||||||
|
| 5 | 결과회신(자체종결) | `LE→EP` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/tech-review` | 기존 |
|
||||||
|
| 6 | 기술검토 의뢰 | `LE→FV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-examine` | **신규** |
|
||||||
|
| 7 | 파일검증 콜백 | `FV→ER` | as | `POST /api/v1/dcrag/documents/verify-result` | 서버 |
|
||||||
|
| 8 | 파일보완 팝업 | `ER` | as | `GET /api/v1/fnrs-cnsltn-mng/{docNo}/file-vrfc-results` | 기존 |
|
||||||
|
| 9 | 파일보완요청 | `ER→SL` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/reqsupplement` | 기존 |
|
||||||
|
| 10 | 파일검증 재실행 | `ER` | as | `POST /api/v1/dcrag-to/documents/transfer` | 기존 |
|
||||||
|
| 11 | 검토자지정 | `ER→AC` | elapr | `POST /api/v1/elapr-dmnd` | 기존 |
|
||||||
|
| 12 | 초안 콜백 | `AC→TE` | as | `POST /api/v1/dcrag/draft/complete` | 서버 |
|
||||||
|
| 13 | 실무협의서관리 | `TE` | as | `GET /api/v1/pwrk-cnsltn-mng/updocno/{upDocNo}` | 기존 |
|
||||||
|
| 14 | 검토결과조회 | `TE` | as | `GET /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
|
||||||
|
| 15 | 실무협의서생성 | `TE` | as | `POST /api/v1/dcrag-to/documents/draft/generate` | 기존 |
|
||||||
|
| 16 | 저장(실무) | `TE` | as | `POST /api/v1/pwrk-cnsltn-mng/save-apply` | 기존 |
|
||||||
|
| 17 | 검토완료 | `TE` | elapr | `PUT /api/v1/elapr-prgrs-stts/{id}/process` | 기존 |
|
||||||
|
| 18 | 보완요청 | `TE→SL` | as | `POST .../reqsupplement` | 기존 |
|
||||||
|
| 19 | 결재(승인) | `TE` | elapr | `PUT .../process` | 기존 |
|
||||||
|
| 20 | 결재(회신) | `TE→TP` | elapr | `PUT .../process` | 기존 |
|
||||||
|
| 21 | 파일보완완료 | `SL→ER` | as | `POST .../submitsupplement` | 기존 |
|
||||||
|
| 22 | 보완완료(기술) | `SL→TE` | as | 동일 | 기존 |
|
||||||
|
| 23 | 보완완료(조치) | `SL→RK` | as | 동일 | 기존 |
|
||||||
|
| 24 | 재원협의 결과 회신 | `TP→RM` | as | `POST .../{docNo}/tech-review` | 기존 |
|
||||||
|
| 25 | 지자체회신 | `RM→RB` | as | `POST .../doc/updatePrgrsStts` | 기존 |
|
||||||
|
| 26 | 실무협의서 관리 | `RB` | as | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | 기존 |
|
||||||
|
| 27 | 조치완료 | `RB→RA` | as | `POST /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit=true`) | **신규** |
|
||||||
|
| 28 | 조치결과서제출 | `RC→RA` | as | `updatePrgrsStts` | 기존 |
|
||||||
|
| 29 | 조치결과 검토의뢰 | `RA→AV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-actn-review` | **신규** |
|
||||||
|
| 30 | 금액검증 콜백 | `AV→EV` | as | `POST /api/v1/dcrag/cost/verify-result` | 서버 |
|
||||||
|
| 30a | 검토자지정(2차) | `EV→RK` | elapr | `POST /api/v1/elapr-dmnd` (`elaprCycl=2`) | 기존 |
|
||||||
|
| 31 | 실무협의서관리 | `RK` | as | `GET /pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
|
||||||
|
| 32 | 금액검증 재실행 | `RK` | as | `POST /api/v1/dcrag-to/documents/cost/verify` | 기존 |
|
||||||
|
| 33 | 보완요청(금액) | `RK→SL` | as | `reqsupplement` | 기존 |
|
||||||
|
| 34 | 결재(승인) | `RK` | elapr | `process` | 기존 |
|
||||||
|
| 35 | 결재(회신) | `RK→RR` | elapr | `process` | 기존 |
|
||||||
|
| 36 | 결과회신 | `RR→EP` | as | `updatePrgrsStts` | 기존 |
|
||||||
|
| 37 | 결과회신 | `EP→TA` | as | `updatePrgrsStts` | 기존 |
|
||||||
|
| 38 | 결과회신(확인) | `TA` | — | 없음 | — |
|
||||||
|
| W1 | 웹기안기 Open | — | as | `GET /api/v1/pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | 기존 |
|
||||||
|
| W2 | HWPX 저장 | — | as | `POST /api/v1/pwrk-cnsltn-doc-crt-rslt` multipart | 기존 |
|
||||||
|
| W3 | 다운로드 | — | as | `GET /api/v1/pwrk-cnsltn-doc-crt-rslt/{uuid}/download` | 기존 |
|
||||||
|
| E-N | 결재선 복사 | `AV→RK` 부수 | elapr | `POST /api/v1/elapr-apln/copy-line` | **신규·서버** |
|
||||||
|
|
||||||
|
조회 공통: `GET /fnrs-cnsltn-mng`, `GET /fnrs-cnsltn-mng/{docNo}`, `GET /pwrk-cnsltn-mng`, `GET /dcrag-to/documents/draft/status/{crtDmndUuid}`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 공통 요청
|
||||||
|
|
||||||
|
### `DocPrgrsUpdateRequestDto`
|
||||||
|
|
||||||
|
`POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` — `ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"docNo": "F-20260824-0001",
|
||||||
|
"docSeComCd": "FNRS",
|
||||||
|
"prgrsSttsComCd": "LE",
|
||||||
|
"rgtrUuid": "optional-uuid",
|
||||||
|
"rmrk": "optional"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
응답: `204/200` void. 프론트 엔티티: 기존 `changePrgrsSttsComCd`.
|
||||||
|
|
||||||
|
### `FnrsCnsltnApplySaveRequestDto`
|
||||||
|
|
||||||
|
`save-apply`, `tech-review`, `reqsupplement`, `submitsupplement`가 공유. 핵심은 `fnrsCnsltnMng.prgrsSttsComCd`와 중첩 리스트(`bzScaleList`, `bizCstList`, `cklstResultList`, `esntlAtchFileList`, `splmntDmndList`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 단계별 FE API
|
||||||
|
|
||||||
|
### 3-1. `SV → ST → LE`
|
||||||
|
|
||||||
|
**저장 / 신청완료** — `POST /fnrs-cnsltn-mng/save-apply`
|
||||||
|
권한: `ROLE_USER05`. 임시저장은 `prgrsSttsComCd=SV`, 신청은 `ST`.
|
||||||
|
응답: `string` (문서번호). 기존 `fnrsCnsltnMngService.saveApply`.
|
||||||
|
|
||||||
|
**삭제** — `DELETE /fnrs-cnsltn-mng/{docNo}` (`SV`만).
|
||||||
|
|
||||||
|
**환경청제출** — `updatePrgrsStts` `LE`. 권한: `ROLE_USER04`.
|
||||||
|
|
||||||
|
### 3-2. `LE → FV | EP`
|
||||||
|
|
||||||
|
**자체종결** — `POST /fnrs-cnsltn-mng/{docNo}/tech-review`
|
||||||
|
Body: `FnrsCnsltnApplySaveRequestDto` (환경청 의견·체크리스트). `LE→EP`.
|
||||||
|
기존 `techReview`. 현재 화면은 `TP`에서도 이 API를 쓰며 목표 상태를 `EP`로 넣고 있다 → **`TP`는 `RM`으로 바꿔야 함**.
|
||||||
|
|
||||||
|
**기술검토 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-examine`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "prgrsSttsComCd": "FV" }
|
||||||
|
```
|
||||||
|
|
||||||
|
응답: `string` (docNo). `LE→FV` + AI 파일검증 + 30분 타이머(서버).
|
||||||
|
프론트: `fnrsCnsltnMngService.requestExamine(docNo, body)` 추가. **`updatePrgrsStts("ER")` 금지.**
|
||||||
|
|
||||||
|
### 3-3. `FV → ER → SL → ER`
|
||||||
|
|
||||||
|
파일검증 콜백은 서버. FE는 `FV`에서 상세 refetch.
|
||||||
|
|
||||||
|
**검증결과 목록** — `GET /fnrs-cnsltn-mng/{fnrsCnsltnDocNo}/file-vrfc-results`
|
||||||
|
응답: `FnrsCnsltnFileVrfcRsltResponseDto[]`
|
||||||
|
|
||||||
|
| 필드 | 설명 |
|
||||||
|
| --- | --- |
|
||||||
|
| `fileSeComCd` | `ESSENTIAL` / `ADDITIONAL` |
|
||||||
|
| `fileSeNm` | 구분명 |
|
||||||
|
| `esntlAtchFileId` | 필수첨부 ID |
|
||||||
|
| `atchFileNm` | 파일명 |
|
||||||
|
| `vrfcRsltComCd` | `PASS` / `FAIL` / `WARN` |
|
||||||
|
| `vrfcOpnn` | 검증의견 |
|
||||||
|
|
||||||
|
엔티티에 `findFileVrfcResults` 추가. 첨부 행 배지는 상세 `esntlFileList`/`etcFileList`의 `vrfcSttsComCd`, `vrfcRsltCn`, `vrfcDt`도 사용 (타입 확장 필요).
|
||||||
|
|
||||||
|
**파일보완요청** — `POST .../{docNo}/reqsupplement` (`ER→SL`).
|
||||||
|
**파일보완완료** — `POST .../{docNo}/submitsupplement` (`SL→ER`). 복귀는 서버.
|
||||||
|
|
||||||
|
**파일검증 재실행** — `POST /dcrag-to/documents/transfer`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "fnrsCnsltnDocNo": "F-20260824-0001", "taskSeComCd": "SF", "taskSeCdNm": "하수처리시설" }
|
||||||
|
```
|
||||||
|
|
||||||
|
응답: `{ resultCode, message, requestId }`. 상태 유지. 기존 `dcRagApiToService.requestDocumentTransfer`.
|
||||||
|
|
||||||
|
### 3-4. `ER → AC → TE → TP`
|
||||||
|
|
||||||
|
**검토자지정** — `POST /elapr-dmnd`
|
||||||
|
기존 `elaprDmndService.save`.
|
||||||
|
|
||||||
|
| 필드 | 값 |
|
||||||
|
| --- | --- |
|
||||||
|
| `elaprDocNo` | 재원협의 번호 |
|
||||||
|
| `elaprCycl` | `1` |
|
||||||
|
| `docPrgrsSttsComCd` | **`AC`** (현행은 현재 상태 `ER`를 넣고 있음 → 수정) |
|
||||||
|
| `elaprPrgrsSttsList[].atrzOrdr` | 1 검토자, 2 승인자, 3 최종 |
|
||||||
|
|
||||||
|
서버가 실무협의 문서 생성 + RAG 초안 트리거 + 30분 타이머. 응답의 문서번호/`crtDmndUuid`가 있으면 폴링 키로 쓴다. 없으면 `findByUpDocNo`로 복원.
|
||||||
|
|
||||||
|
**초안 상태 폴링** — `GET /dcrag-to/documents/draft/status/{crtDmndUuid}`
|
||||||
|
기존 `getDraftGenerationStatus`. 주기 5~10초. 종료 `COMPLETED`/`FAILED`.
|
||||||
|
|
||||||
|
**수동 초안생성** — `POST /dcrag-to/documents/draft/generate` (`TE` 유지)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fnrsCnsltnDocNo": "F-...",
|
||||||
|
"pwrkCnsltnDocNo": "P-...",
|
||||||
|
"crtDmndUuid": "optional"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
202 + `DcRagDraftGenerateResponseDto` (`crtDmndUuid`, `crtSttsComCd`, `estimatedMinutes`).
|
||||||
|
v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**. 1차 트리거는 검토자지정이고, 이 API는 Fallback 전용.
|
||||||
|
|
||||||
|
**실무 저장** — `POST /pwrk-cnsltn-mng/save-apply` → `string`.
|
||||||
|
**보완요청/완료** — fnrs `reqsupplement` / `submitsupplement`.
|
||||||
|
|
||||||
|
**결재** — `PUT /elapr-prgrs-stts/{elaprPrgrsId}/process`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"atrzPrgrsSttsComCd": "ATRZ_PRGRS_003",
|
||||||
|
"atrzOpnnCn": "의견",
|
||||||
|
"finalApprovalYn": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
반려: `ATRZ_PRGRS_004`. 기존 `elaprPrgrsSttsService.process`. 최종 시 서버가 `TP`/`RR` 전이.
|
||||||
|
|
||||||
|
### 3-5. `TP → RM → RB → RA`
|
||||||
|
|
||||||
|
**환경청 1차 회신** — `tech-review` (`TP→RM`).
|
||||||
|
**광역 지자체회신** — `updatePrgrsStts` `RB`.
|
||||||
|
|
||||||
|
**조치완료 (신규)** — `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"isSubmit": true,
|
||||||
|
"actnList": [
|
||||||
|
{
|
||||||
|
"rvwOpnnSn": 1,
|
||||||
|
"actnRsltCn": "수용 — 관로 연장 재산정",
|
||||||
|
"atchFileGroupId": "optional"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totActnSummary": "제출 시 권장",
|
||||||
|
"muniDocNo": "제출 시 권장"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isSubmit: false` → `RB` 유지 (API만 존재, **화면 버튼 없음**. HWP·첨부는 실무협의서 관리 다이얼로그에서 저장)
|
||||||
|
- `isSubmit: true` → `RB→RA` (조치완료)
|
||||||
|
응답: `string` (pwrkDocNo). DTO명 `PwrkActnResultSubmitDto`.
|
||||||
|
|
||||||
|
### 3-6. `RA → AV → EV → RK`
|
||||||
|
**2차 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"specialRqstCn": "중점 검증 요청 (선택)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`RA→AV`. 응답: `string` (docNo). DTO명 `EnvoActnReviewReqDto`. `specialRqstCn`만 선택.
|
||||||
|
|
||||||
|
금액검증 콜백은 서버 → `AV→EV`.
|
||||||
|
**2차 검토자지정** — `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선(`findDocPrgrsByDocNo`, cycl=1)을 시드로 편집.
|
||||||
|
|
||||||
|
**금액검증 재실행** — `POST /dcrag-to/documents/cost/verify`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fnrsCnsltnDocNo": "F-...",
|
||||||
|
"pwrkCnsltnDocNo": "P-..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
202. 기존 `requestCostVerification`. `RK` 유지.
|
||||||
|
|
||||||
|
### 3-7. `RK → RR | SL`
|
||||||
|
|
||||||
|
보완은 3-4와 동일 API. 팝업만 금액 체크.
|
||||||
|
결재 `process` 2차 최종 → 서버가 `RR`.
|
||||||
|
|
||||||
|
### 3-8. `RR → EP → TA`
|
||||||
|
|
||||||
|
둘 다 `updatePrgrsStts`. `TA` 확인은 API 없음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 웹기안기
|
||||||
|
|
||||||
|
설계서 기준 **기존**. 구현 시 컨트롤러 존재 여부를 확인한다.
|
||||||
|
|
||||||
|
| ID | 용도 | URI | 응답 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| W1 | 웹기안기 Open | `GET .../pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/webhwp/open` | `inline` HWPX blob → `HwpCtrl.Open(File)` |
|
||||||
|
| W2 | 편집 저장 | `POST .../pwrk-cnsltn-doc-crt-rslt` multipart `file` + `crtDmndUuid` | 201, `verSn+1` |
|
||||||
|
| W3 | PC 저장 | `GET .../{crtDmndUuid}/download` | `attachment` blob |
|
||||||
|
|
||||||
|
버전 지정: `GET .../{crtDmndUuid}/{verSn}/webhwp/open`, `.../{verSn}/download`.
|
||||||
|
`/{uuid}/latest`, `/{uuid}/download`가 `/{uuid}/{verSn}`보다 우선. path를 섞지 말 것.
|
||||||
|
|
||||||
|
다운로드:
|
||||||
|
|
||||||
|
- axios `responseType: "blob"`
|
||||||
|
- 파일명: `Content-Disposition` `filename*=UTF-8''...` (`parseFilenameFromResponse`)
|
||||||
|
- 서버 파일명: `재원협의 실무협의서({bizNm})_v{verSn}.hwp`
|
||||||
|
- Content-Type은 `application/x-hwp` 또는 `application/haansofthwpx`일 수 있음. 타입에 의존하지 말 것
|
||||||
|
|
||||||
|
Open은 axios `responseType: "blob"`으로 인증된 API를 호출하고, 응답 Blob을 HWPX `File`로 감싸 `HwpCtrl.Open`에 전달한다. `blob:` Object URL은 사용하지 않는다.
|
||||||
|
|
||||||
|
multipart `Content-Type` 헤더를 수동 지정하지 않는다 (boundary). JSON `saveJson`은 UI에서 쓰지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 전자결재 (`as-elapr-service`)
|
||||||
|
|
||||||
|
| API | URI | FE |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 검토자지정·1차 결재요청 | `POST /elapr-dmnd` | O |
|
||||||
|
| 결재 처리 | `PUT /elapr-prgrs-stts/{id}/process` | O |
|
||||||
|
| 결재선 복사 | `POST /elapr-apln/copy-line` | **X (서버)** |
|
||||||
|
|
||||||
|
`copy-line` body (참고만):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"docNo": "P-20260827-0001",
|
||||||
|
"sourceElaprCycl": 1,
|
||||||
|
"targetElaprCycl": 2,
|
||||||
|
"rgtrUuid": "system-uuid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`RK`에서 결재현황은 `findDocPrgrsByDocNo`(전체) 조회 후 `elaprCycl`로 1차/2차 탭 표시.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 프론트 호출 금지
|
||||||
|
|
||||||
|
| URI | 이유 |
|
||||||
|
| --- | --- |
|
||||||
|
| `POST /dcrag/documents/verify-result` | AI→AS 콜백 |
|
||||||
|
| `POST /dcrag/draft/complete` | AI→AS 콜백 |
|
||||||
|
| `POST /dcrag/cost/verify-result` | AI→AS 콜백. 여기서 copy-line도 호출 |
|
||||||
|
| `POST /elapr-apln/copy-line` | 콜백 Feign |
|
||||||
|
| `POST /pwrk-cnsltn-mng` (`save` 초안 트리거) | v3.0 1차 트리거는 `elapr-dmnd`. 컨트롤러가 남아 있어도 화면에서 쓰지 않음 |
|
||||||
|
|
||||||
|
`POST /dcrag-to/documents/draft/generate`는 **Fallback에서만** 허용.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Gap Analysis (프론트 엔티티)
|
||||||
|
|
||||||
|
### 이미 있는 것
|
||||||
|
|
||||||
|
`fnrsCnsltnMngService`: `findPage`, `findById`, `save`, `saveApply`, `update`, `techReview`, `submitSupplement`, `submitReqSupplement`, `remove`, `changePrgrsSttsComCd`
|
||||||
|
`dcRagApiToService`: `requestDocumentTransfer`, `requestDraftGeneration`, `getDraftGenerationStatus`, `requestCostVerification`
|
||||||
|
`elaprDmndService.save`, `elaprPrgrsSttsService.process`
|
||||||
|
|
||||||
|
### 추가할 것
|
||||||
|
|
||||||
|
| 메서드 | URI |
|
||||||
|
| --- | --- |
|
||||||
|
| `requestExamine` | `POST /fnrs-cnsltn-mng/{id}/request-examine` |
|
||||||
|
| `findFileVrfcResults` | `GET /fnrs-cnsltn-mng/{id}/file-vrfc-results` |
|
||||||
|
| `requestActnReview` | `POST /fnrs-cnsltn-mng/{id}/request-actn-review` |
|
||||||
|
| `pwrkCnsltnMngService.*` | 슬라이스 신규 |
|
||||||
|
| `submitActnResult` | `POST /pwrk-cnsltn-mng/{id}/submit-actn-result` |
|
||||||
|
| `pwrkCnsltnDocCrtRsltService.*` | 슬라이스 신규 |
|
||||||
|
|
||||||
|
`copyLine`은 프론트 서비스에 넣지 않는다.
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
# 레퍼런스 — 프론트 엔티티·DTO
|
||||||
|
|
||||||
|
> 상위: [개발가이드.md](./개발가이드.md)
|
||||||
|
> 엔티티 규칙: `frontend/.cursor/skills/backend-controller-to-entity`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. ENDPOINT
|
||||||
|
|
||||||
|
`apps/as/src/shared/config/api.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
AS_SERVICE: {
|
||||||
|
// 기존
|
||||||
|
FNRS_CNSLTN_MNG: "/as-service/api/v1/fnrs-cnsltn-mng",
|
||||||
|
DCRAG_TO: "/as-service/api/v1/dcrag-to",
|
||||||
|
// 추가
|
||||||
|
PWRK_CNSLTN_MNG: "/as-service/api/v1/pwrk-cnsltn-mng",
|
||||||
|
PWRK_CNSLTN_DOC_CRT_RSLT: "/as-service/api/v1/pwrk-cnsltn-doc-crt-rslt",
|
||||||
|
}
|
||||||
|
|
||||||
|
AS_ELAPR_SERVICE: {
|
||||||
|
ELAPR_TMPLT: "/as-elapr-service/api/v1/elapr-tmplt",
|
||||||
|
ELAPR_APLN: "/as-elapr-service/api/v1/elapr-apln",
|
||||||
|
ELAPR_DMND: "/as-elapr-service/api/v1/elapr-dmnd",
|
||||||
|
ELAPR_PRGRS_STTS: "/as-elapr-service/api/v1/elapr-prgrs-stts",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`copy-line`은 `ELAPR_APLN` 하위이나 FE에서 호출하지 않음.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 슬라이스 배치
|
||||||
|
|
||||||
|
```
|
||||||
|
entities/fnrs-cnsltn/ # 확장
|
||||||
|
entities/dcrag-to/ # 구현됨
|
||||||
|
entities/elapr/ # 구현됨
|
||||||
|
entities/pwrk-cnsltn-mng/ # 신규
|
||||||
|
entities/pwrk-cnsltn-doc-crt-rslt/ # 신규
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
entities/pwrk-cnsltn-mng/
|
||||||
|
├── model/
|
||||||
|
│ ├── pwrk-cnsltn-mng-interface.ts
|
||||||
|
│ └── pwrk-cnsltn-mng-payload-schema.ts
|
||||||
|
├── api/
|
||||||
|
│ ├── pwrk-cnsltn-mng-service.ts
|
||||||
|
│ └── queries.ts
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
`submit-actn-result` Request는 zod SSOT (`PwrkActnResultSubmitDto`).
|
||||||
|
`request-examine` / `request-actn-review`도 fnrs payload-schema에 추가.
|
||||||
|
|
||||||
|
라우트:
|
||||||
|
|
||||||
|
| 화면 | 경로 |
|
||||||
|
| --- | --- |
|
||||||
|
| 재원협의 목록/상세/작성 | 기존 `app/(main)/(with-base-layout)/fnrs-cnsltn/...` |
|
||||||
|
| 실무협의 목록 | `.../pwrk-cnsltn/page.tsx` (선택) |
|
||||||
|
| 실무협의 상세 | `.../pwrk-cnsltn/[id]/page.tsx` (선택) |
|
||||||
|
| 페이지 UI | `pages/fnrs-cnsltn/`, `pages/pwrk-cnsltn/` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Service 시그니처
|
||||||
|
|
||||||
|
`fnrsCnsltnMngService`에 추가:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
requestExamine(docNo: string, body: FnrsExamineRequestDto): Promise<string>;
|
||||||
|
findFileVrfcResults(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnFileVrfcRsltResponseDto[]>;
|
||||||
|
requestActnReview(docNo: string, body: EnvoActnReviewReqDto): Promise<string>;
|
||||||
|
```
|
||||||
|
|
||||||
|
실무협의:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface PwrkCnsltnMngService {
|
||||||
|
findPage(searchDto: PwrkCnsltnMngSearchDto): Promise<PageResponse<PwrkCnsltnMngResponseDto>>;
|
||||||
|
findById(pwrkCnsltnDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
|
||||||
|
findByUpDocNo(upDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
|
||||||
|
saveApply(body: PwrkCnsltnApplySaveRequestDto): Promise<string>;
|
||||||
|
submitActnResult(
|
||||||
|
pwrkCnsltnDocNo: string,
|
||||||
|
body: PwrkActnResultSubmitDto,
|
||||||
|
): Promise<string>;
|
||||||
|
update(pwrkCnsltnDocNo: string, body: PwrkCnsltnMngUpdateRequestDto): Promise<string>;
|
||||||
|
remove(pwrkCnsltnDocNo: string): Promise<void>;
|
||||||
|
changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
v1.0의 `save(): PwrkCnsltnMngSaveResponseDto`(초안 트리거)는 화면에서 사용하지 않는다. 컨트롤러가 남아 있으면 엔티티에 넣더라도 UI 연결 금지.
|
||||||
|
|
||||||
|
생성결과:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface PwrkCnsltnDocCrtRsltService {
|
||||||
|
findPage(
|
||||||
|
searchDto: PwrkCnsltnDocCrtRsltSearchDto,
|
||||||
|
): Promise<PageResponse<PwrkCnsltnDocCrtRsltResponseDto>>;
|
||||||
|
findById(crtDmndUuid: string, verSn: number): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
|
||||||
|
findLatestByCrtDmndUuid(crtDmndUuid: string): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
|
||||||
|
openWebHwpLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
|
||||||
|
download(crtDmndUuid: string, verSn: number): Promise<AxiosResponse<Blob>>;
|
||||||
|
downloadLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
|
||||||
|
saveMultipart(formData: FormData): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
|
||||||
|
updateMultipart(
|
||||||
|
crtDmndUuid: string,
|
||||||
|
verSn: number,
|
||||||
|
formData: FormData,
|
||||||
|
): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
|
||||||
|
remove(crtDmndUuid: string, verSn: number): Promise<void>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`webhwp/open`은 인증된 axios 요청으로 Blob을 조회한다:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async function openWebHwpLatest(crtDmndUuid: string) {
|
||||||
|
return client.get<Blob>(`${BASE}/${crtDmndUuid}/webhwp/open`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
응답 Blob은 HWPX `File`로 감싸 `HwpCtrl.Open(file, "HWPX", ...)`에 전달한다. `blob:` Object URL은 사용하지 않는다.
|
||||||
|
|
||||||
|
DC RAG TO (구현됨, 시그니처 유지):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
requestDocumentTransfer(body: DcRagDocumentTransferRequestDto): Promise<DcRagCommonResponseDto>;
|
||||||
|
requestDraftGeneration(body: DcRagDraftGenerateRequestDto): Promise<DcRagDraftGenerateResponseDto>;
|
||||||
|
getDraftGenerationStatus(crtDmndUuid: string): Promise<DcRagDraftStatusResponseDto>;
|
||||||
|
requestCostVerification(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto>;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. DTO
|
||||||
|
|
||||||
|
타입명 = 백엔드 클래스명. Save/Apply는 zod.
|
||||||
|
|
||||||
|
### 4-1. 재원협의 추가
|
||||||
|
|
||||||
|
**`FnrsExamineRequestDto`**
|
||||||
|
|
||||||
|
| 필드 | 필수 | 값 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `prgrsSttsComCd` | N | 기본 `FV` |
|
||||||
|
|
||||||
|
**`EnvoActnReviewReqDto`**
|
||||||
|
|
||||||
|
| 필드 | 필수 |
|
||||||
|
| --- | --- |
|
||||||
|
| `specialRqstCn` | N |
|
||||||
|
|
||||||
|
**`FnrsCnsltnFileVrfcRsltResponseDto`**: [ref-endpoint.md](./ref-endpoint.md) §3-3.
|
||||||
|
|
||||||
|
첨부 검증 배지용으로 `AtflMngClientResponseDto` / `EsntlAtchFileMngTreeResponseDto`에 `vrfcDt`, `vrfcRsltCn`, `vrfcSttsComCd` 확장.
|
||||||
|
|
||||||
|
### 4-2. `PwrkActnResultSubmitDto` (zod)
|
||||||
|
|
||||||
|
| 필드 | 필수 | 비고 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `isSubmit` | Y | `false` API만(화면 버튼 없음), `true` 조치완료(`RB→RA`) |
|
||||||
|
| `actnList` | N | 조치완료 시 선택 |
|
||||||
|
| `actnList[].rvwOpnnSn` | Y (항목 있을 때) | |
|
||||||
|
| `actnList[].actnRsltCn` | N | |
|
||||||
|
| `actnList[].atchFileGroupId` | N | |
|
||||||
|
| `totActnSummary` | N | 제출 시 권장 |
|
||||||
|
| `muniDocNo` | N | 제출 시 권장 |
|
||||||
|
| `pwrkAtchFileGroupId` | N | |
|
||||||
|
|
||||||
|
### 4-3. `PwrkCnsltnMng*`
|
||||||
|
|
||||||
|
**Search**: `pwrkCnsltnDocNo`, `upDocNo`, `upDocSeComCd`, `prgrsSttsComCd`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `bizNm`
|
||||||
|
|
||||||
|
**DetResponse 중첩**
|
||||||
|
|
||||||
|
- `cnsltnList`: 협의내역 (`rvwOpnnSn`, `rvwOpnnClsfComCd`, `cnsltnCn`, `rfltYn`, …)
|
||||||
|
- `pjtcoAjmtList`: 사업비조정
|
||||||
|
- `crtDmndList[].crtRslt`: 생성요청 + 최신 결과
|
||||||
|
- `splmntDmndList`: 보완요청
|
||||||
|
- `sttsChgHstryList`: 상태변경이력 — `SL` 복귀 라벨에 사용
|
||||||
|
- `trsmFileList`: 전송파일 (Java Det DTO 확인 후 반영)
|
||||||
|
|
||||||
|
마스터 조인: `bizNm`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `aplcntNm` 등.
|
||||||
|
|
||||||
|
`crtRslt.orgnlOtptDocCn`은 항상 `null`.
|
||||||
|
|
||||||
|
**상세 예**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pwrkCnsltnDocNo": "P-20260827-0001",
|
||||||
|
"upDocNo": "F-20260824-0001",
|
||||||
|
"upDocSeComCd": "FNRS",
|
||||||
|
"prgrsSttsComCd": "TE",
|
||||||
|
"cnsltnList": [],
|
||||||
|
"pjtcoAjmtList": [],
|
||||||
|
"crtDmndList": [
|
||||||
|
{
|
||||||
|
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
|
||||||
|
"pwrkCnsltnDocNo": "P-20260827-0001",
|
||||||
|
"rqstrUuid": "...",
|
||||||
|
"rqstrNm": "관리자",
|
||||||
|
"crtSttsComCd": "C",
|
||||||
|
"crtRslt": {
|
||||||
|
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
|
||||||
|
"verSn": 1,
|
||||||
|
"rgtrNm": "관리자",
|
||||||
|
"orgnlOtptDocCn": null,
|
||||||
|
"crtCmptnDt": "2026-08-27 10:47:01"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"splmntDmndList": [],
|
||||||
|
"sttsChgHstryList": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4-4. `PwrkCnsltnDocCrtRslt*`
|
||||||
|
|
||||||
|
복합 PK: `crtDmndUuid` + `verSn`
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 비고 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `crtDmndUuid` | `string` | |
|
||||||
|
| `verSn` | `number` | |
|
||||||
|
| `rgtrUuid` / `rgtrNm` | `string` | |
|
||||||
|
| `orgnlOtptDocCn` | `null` | 목록/상세 미포함 |
|
||||||
|
| `crtCmptnDt` | `string` | |
|
||||||
|
|
||||||
|
**Search** (`PageableParams`): `crtDmndUuid`(이력 조회 시 필수), `pwrkCnsltnDocNo`, `verSn`, `rgtrUuid`
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt?page=0&size=10&crtDmndUuid={uuid}&sort=verSn,desc
|
||||||
|
```
|
||||||
|
|
||||||
|
**Save (zod)**: `crtDmndUuid` 필수. `rgtrUuid` 생략 시 서버 로그인 사용자. `file`은 multipart part (DTO 필드 아님).
|
||||||
|
|
||||||
|
### 4-5. DC RAG (구현됨)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface DcRagDocumentTransferRequestDto {
|
||||||
|
fnrsCnsltnDocNo: string;
|
||||||
|
taskSeComCd?: string;
|
||||||
|
taskSeCdNm?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DcRagDraftStatusResponseDto {
|
||||||
|
crtDmndUuid: string;
|
||||||
|
status: DraftStatus;
|
||||||
|
statusName?: string;
|
||||||
|
progressPercent?: number;
|
||||||
|
events?: EventInfoDto[];
|
||||||
|
startedDt?: string;
|
||||||
|
estimatedCompletionDt?: string;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DraftStatus =
|
||||||
|
| "REQUESTED"
|
||||||
|
| "DOWNLOADING"
|
||||||
|
| "ANALYZING"
|
||||||
|
| "GENERATING"
|
||||||
|
| "COMPLETED"
|
||||||
|
| "FAILED";
|
||||||
|
```
|
||||||
|
|
||||||
|
`estimatedMinutes`는 초안 응답에 있을 수 있으나 UX 안내는 **30분 Fallback**을 따른다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 폴링 훅
|
||||||
|
|
||||||
|
초안 (`AC` 또는 `TE`이면서 생성 중):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
useQuery({
|
||||||
|
queryKey: [...dcragToKeys.draftStatus(crtDmndUuid)],
|
||||||
|
queryFn: () => dcRagApiToService.getDraftGenerationStatus(crtDmndUuid),
|
||||||
|
enabled: Boolean(crtDmndUuid) && isPolling,
|
||||||
|
refetchInterval: (query) => {
|
||||||
|
const status = query.state.data?.status;
|
||||||
|
if (status === "COMPLETED" || status === "FAILED") return false;
|
||||||
|
return 5_000;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`COMPLETED` 시 `fnrs-cnsltn` 상세·`pwrk-cnsltn-mng` `findByUpDocNo` invalidate.
|
||||||
|
|
||||||
|
`FV`/`AV`는 `findById`를 5~10초 refetch. `prgrsSttsComCd`가 `ER`/`RK`가 되면 중단. 시작 시각 기준 30분 경과 안내 문구.
|
||||||
|
|
||||||
|
`crtDmndUuid` 복원: `findByUpDocNo(fnrsCnsltnDocNo).crtDmndList[0]`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 다운로드
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const response = await client.get<Blob>(url, { responseType: "blob" });
|
||||||
|
const filename = parseFilenameFromResponse(response);
|
||||||
|
const objectUrl = window.URL.createObjectURL(response.data);
|
||||||
|
```
|
||||||
|
|
||||||
|
기존 `attachService`와 동일.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 실무협의 목록 컬럼 (선택 화면)
|
||||||
|
|
||||||
|
| 표시명 | Field |
|
||||||
|
| --- | --- |
|
||||||
|
| 실무협의번호 | `pwrkCnsltnDocNo` |
|
||||||
|
| 재원협의번호 | `upDocNo` |
|
||||||
|
| 진행상태 | `prgrsSttsComCdNm` |
|
||||||
|
| 사업명 | `bizNm` |
|
||||||
|
| 시도/구군 | `sido` / `gugun` |
|
||||||
|
| 신청자 | `aplcntNm` |
|
||||||
|
| 등록일 | `regDt` |
|
||||||
|
|
||||||
|
행 클릭 → `/pwrk-cnsltn/{pwrkCnsltnDocNo}` 또는 재원협의 상세로 이동 (화면설계 확정 후).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. `elapr-dmnd` 저장 시 주의
|
||||||
|
|
||||||
|
현행 `floating-action-buttons.tsx`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
elaprCycl: data.detDto?.prgrsSttsComCd === "RK" ? 2 : 1,
|
||||||
|
docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd, // ER
|
||||||
|
```
|
||||||
|
|
||||||
|
To-Be:
|
||||||
|
|
||||||
|
- 검토자지정은 **`ER`(1차)** 과 **`EV`(2차)** 에서.
|
||||||
|
- `ER`: `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"`, 서식 선택 다이얼로그
|
||||||
|
- `EV`: `elaprCycl: 2`, `docPrgrsSttsComCd: "RK"`, 1차 결재선 시드 다이얼로그(서식 없음)
|
||||||
|
|
||||||
|
`RK` / ATRZ02 FAB: `findDocPrgrsByDocNo` 전체 조회 후 pending은 `elaprCycl === 2`만 사용. 검토완료 후 `ElaprApprovalDialog`에 `elaprCycl={2}`. 보완요청은 TE와 동일 `SplmntDmndDialog`.
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# 레퍼런스 — 프로세스·상태 전이
|
||||||
|
|
||||||
|
> 상위: [개발가이드.md](./개발가이드.md)
|
||||||
|
> SoT: `03.재원협의_실무협의_프로세스_통합_v완결.md` (v3.0, 2026-09-03)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 프로세스 개요
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| --- | --- |
|
||||||
|
| Process No. | PR-AS-003 |
|
||||||
|
| 메뉴 | 행정지원 > 2.2.2 재원협의 > 2.2.2.1 재원협의 관리 |
|
||||||
|
| 목적 | 기초 → 광역(경유) → 환경청(승인) → 공단(기술검토/AI) 4자 협의로 사업규모·국비·분담금을 확정 |
|
||||||
|
|
||||||
|
2-Pass:
|
||||||
|
|
||||||
|
- **Pass 1** 기술 타당성: `SV→ST→LE→FV→ER→AC→TE→TP→RM→RB`
|
||||||
|
- **Pass 2** 조치결과·금액: `RB→RA→AV→EV→RK→RR→EP→TA`
|
||||||
|
|
||||||
|
자체종결(기술검토 생략): `LE→EP→TA`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 4-Lane 시퀀스
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
autonumber
|
||||||
|
actor 기초 as 기초 (ROLE_USER05)
|
||||||
|
actor 광역 as 광역 (ROLE_USER04)
|
||||||
|
actor 환경청 as 환경청 (ROLE_USER03)
|
||||||
|
actor 공단 as 공단 (ROLE_USER01)
|
||||||
|
actor AI as AI 엔진
|
||||||
|
|
||||||
|
rect rgb(240, 248, 255)
|
||||||
|
기초->>기초: [저장] SV
|
||||||
|
기초->>광역: [신청완료] ST
|
||||||
|
광역->>환경청: [환경청제출] LE
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(255, 250, 240)
|
||||||
|
alt 자체종결
|
||||||
|
환경청->>광역: [결과회신] LE → EP
|
||||||
|
광역->>기초: [결과회신] EP → TA
|
||||||
|
else 기술검토 의뢰
|
||||||
|
환경청->>AI: [기술검토 의뢰] LE → FV
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(245, 255, 245)
|
||||||
|
AI-->>공단: 파일검증 콜백 FV → ER
|
||||||
|
Note over 공단: 30분 미응답 시 ER + 수동 [파일검증]
|
||||||
|
alt 파일 미비
|
||||||
|
공단->>기초: [파일보완] ER → SL
|
||||||
|
기초->>공단: [파일보완완료] SL → ER
|
||||||
|
else 적격
|
||||||
|
공단->>AI: [검토자지정] ER → AC
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(255, 245, 245)
|
||||||
|
AI-->>공단: 초안 콜백 AC → TE
|
||||||
|
Note over 공단: 30분 미응답 시 TE + 수동 [실무협의서생성]
|
||||||
|
alt 설계 미비
|
||||||
|
공단->>기초: [보완요청] TE → SL
|
||||||
|
기초->>공단: [보완완료] SL → TE
|
||||||
|
else 검토 완료
|
||||||
|
공단->>공단: [검토완료] 상신, [결재(승인)] TE 유지
|
||||||
|
공단->>환경청: [결재(회신)] TE → TP
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(250, 240, 255)
|
||||||
|
환경청->>광역: [재원협의 결과 회신] TP → RM
|
||||||
|
광역->>기초: [지자체회신] RM → RB
|
||||||
|
기초->>환경청: [조치완료] RB → RA
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(240, 255, 255)
|
||||||
|
광역->>환경청: [조치결과서제출] RC → RA
|
||||||
|
환경청->>AI: [조치결과 검토의뢰] RA → AV
|
||||||
|
AI-->>공단: 금액검증 콜백 AV → EV
|
||||||
|
공단->>공단: [검토자지정] EV → RK
|
||||||
|
Note over 공단: EV에서 1차 결재선 시드로 2차 결재선 수동 배정
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(255, 255, 230)
|
||||||
|
alt 소명 미비
|
||||||
|
공단->>기초: [보완요청] RK → SL
|
||||||
|
기초->>공단: [보완완료] SL → RK
|
||||||
|
else 확정
|
||||||
|
공단->>환경청: [결재(회신)] RK → RR
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(255, 240, 240)
|
||||||
|
환경청->>광역: [결과회신] RR → EP
|
||||||
|
광역->>기초: [결과회신] EP → TA
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
거버넌스:
|
||||||
|
|
||||||
|
```
|
||||||
|
기초 (USER05) --ST--> 광역 (USER04) --LE--> 환경청 (USER03)
|
||||||
|
기초 <-----TA------ 광역 <-----EP/RM------ 환경청
|
||||||
|
환경청 --FV/ER--> 공단 (USER01) --TP/RR--> 환경청
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Activity 정의 (14)
|
||||||
|
|
||||||
|
| No. | Activity | 주체 | From → To | FE 포인트 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 1 | 재원협의 조회 | 기초 | — | 기본계획·자가점검 대조 |
|
||||||
|
| 2 | 등록 및 제출 | 기초·광역 | `SV→ST`, 광역 `LE` | `save-apply`, `updatePrgrsStts` |
|
||||||
|
| 3 | 접수 및 의뢰 | 환경청 | `LE→FV` / `EP` | 의뢰 vs 자체종결 분기 |
|
||||||
|
| 4 | AI 파일검증 | 공단·AI | `FV→ER` / `SL` | 30분 Fallback, 파일보완 팝업 |
|
||||||
|
| 5 | 자체종결 회신 | 환경청·광역 | `LE→EP→TA` | 공단 레인 없음 |
|
||||||
|
| 6 | 검토자지정·초안 | 공단·AI | `ER→AC→TE` | `elapr-dmnd`, 초안 폴링 |
|
||||||
|
| 7 | 1차 기술검토 | 공단 결재선 | `TE` / `SL` | 웹기안기, HWP 파싱은 서버 |
|
||||||
|
| 8 | 기술검토 회신 | 공단 최종 | `TE→TP` | `process` 최종 |
|
||||||
|
| 9 | 검토결과 회신 | 환경청·광역 | `TP→RM→RB` | 1차 회신. `EP`와 혼동 금지 |
|
||||||
|
| 10 | 조치결과 작성 | 기초 | `RB→RA` | `submit-actn-result` `isSubmit:true` (조치완료) |
|
||||||
|
| 11 | 조치 의뢰 | 환경청 | `RA→AV` | `request-actn-review` |
|
||||||
|
| 12 | AI 금액검증 | AI·공단 | `AV→EV` | 콜백/Fallback 후 접수자 대기 |
|
||||||
|
| 12a | 2차 검토자지정 | 공단 접수 | `EV→RK` | `elapr-dmnd` cycl=2 |
|
||||||
|
| 13 | 2차 검토·결재 | 공단 | `RK→RR` / `SL` | 2차 결재선 |
|
||||||
|
| 14 | 최종 회신 | 환경청·광역·기초 | `RR→EP→TA` | 종결 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `FNRSSTTS` 상세
|
||||||
|
|
||||||
|
관리: `tb_cs_lwpo_cd` / `tb_as_fnrs_cnsltn_mng.prgrs_stts_com_cd`
|
||||||
|
|
||||||
|
| 코드 | 국문 | 영문 | 화면 주체 | 전이 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `SV` | 저장 | Save | 기초 작성 | 신규/`SV` → `SV` |
|
||||||
|
| `ST` | 신청완료 | Submit to Province | 기초→광역 | `SV→ST` |
|
||||||
|
| `LE` | 환경청 검토 | Local Environment Review | 광역→환경청 | `ST→LE` |
|
||||||
|
| `FV` | 파일검증 | File Verification | AI | `LE→FV` |
|
||||||
|
| `ER` | 기술검토 의뢰 | Examine Request | 공단 접수자 | `FV→ER`, `SL→ER` |
|
||||||
|
| `SL` | 보완요청 | Supplement Loop | 공단→기초 | `ER\|TE\|RK → SL` |
|
||||||
|
| `AC` | 초안생성 | AI Auto Creation | AI | `ER→AC` |
|
||||||
|
| `TE` | 기술검토 | Technical Examine | 공단 1차 결재 | `AC→TE`, `SL→TE` |
|
||||||
|
| `TP` | 기술검토 회신 | Technical Pass to Basin | 공단→환경청 | `TE→TP` |
|
||||||
|
| `RM` | 재원협의결과회신 | Result Message to Province | 환경청→광역 | `TP→RM` |
|
||||||
|
| `RB` | 재원협의결과회신(지자체) | Result Back to Municipality | 광역→기초 | `RM→RB` |
|
||||||
|
| `RC` | 조치결과서 제출 | Result Complete | 기초→광역 | `RB→RC` |
|
||||||
|
| `RA` | 조치결과서 제출(환경청) | Result Ask to Basin | 기초→환경청 | `RB→RA` |
|
||||||
|
| `AV` | 금액검증 | Amount Verification | AI | `RA→AV` |
|
||||||
|
| `EV` | 조치결과검토의뢰 | Examine Verify | 공단 접수자 | `AV→EV` |
|
||||||
|
| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `EV→RK`, `SL→RK` |
|
||||||
|
| `RR` | 조치결과서검토 회신 | Result Return to Basin | 공단→환경청 | `RK→RR` |
|
||||||
|
| `EP` | 검토결과 회신 | Examine Pass to Province | 환경청→광역 | `LE→EP`(자체), `RR→EP`(최종) |
|
||||||
|
| `TA` | 검토결과 회신(종료) | Technical Approval | 광역→기초 | `EP→TA` |
|
||||||
|
|
||||||
|
### 4-1. v2.1 → v3.0 대조 (프론트 폐기 대상)
|
||||||
|
|
||||||
|
| 구간 | v2.1 | v3.0 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 의뢰 직후 파일검증 | `ER` | **`FV` → `ER`** |
|
||||||
|
| 검토자지정 후 초안 | `EE` → `DV` | **`ER` → `AC` → `TE`** |
|
||||||
|
| 1차 내부 결재 | `TR` → `TP` | **`TE` 유지 → `TP`** |
|
||||||
|
| 환경청→광역 1차 회신 | `EP` | **`RM`** |
|
||||||
|
| 금액검증 | `RA` | **`RA` → `AV` → `RK`** |
|
||||||
|
| 공단 2차 회신 | `TP` | **`RR`** |
|
||||||
|
| 최종 완료 | `TP` / 고시 `TB` | **`TA`** (고시 없음) |
|
||||||
|
|
||||||
|
가이드 v1.0 `PWRKSTTS`(`DV`, `AR`, `TR`, `AA` 등)도 동일하게 폐기.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 보완(`SL`) 3루프
|
||||||
|
|
||||||
|
복귀 상태는 `tb_as_doc_stts_chg_hstry` 직전 코드. 프론트는 복귀 코드를 요청에 넣지 않고, 라벨만 이력으로 고른다.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
ER --> SL: 파일보완 팝업(파일리스트)
|
||||||
|
SL --> ER: 파일보완완료
|
||||||
|
TE --> SL: 보완 검토팝업()
|
||||||
|
SL --> TE: 보완완료
|
||||||
|
RK --> SL: 보완 검토팝업(금액)
|
||||||
|
SL --> RK: 보완완료
|
||||||
|
```
|
||||||
|
|
||||||
|
| 루프 | 팝업 | 요청 | 제출 버튼 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 파일 무결성 | 파일리스트 + 검증결과 | 공단 접수자 `ER` | [파일보완완료] |
|
||||||
|
| 1차 기술 | 설계 미달·사유 | 공단 검토자 `TE` | [보완완료] |
|
||||||
|
| 2차 금액 | 공종별 금액 체크 | 공단 검토자 `RK` | [보완완료] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. AI 3파이프라인·30분 Fallback
|
||||||
|
|
||||||
|
| ID | 단계 | 진입 → 완료 | 프론트 진입 | Fallback | 수동 재실행 |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| AI-1 | 파일 무결성 | `FV→ER` | 환경청 [기술검토 의뢰] | 30분 → `ER` + [파일검증] | `POST /dcrag-to/documents/transfer` |
|
||||||
|
| AI-2 | 초안 생성 | `AC→TE` | 공단 [검토자지정] | 30분 → `TE` + [실무협의서생성] | `POST /dcrag-to/documents/draft/generate` |
|
||||||
|
| AI-3 | 금액 검증 | `AV→EV` | 환경청 [조치결과 검토의뢰] | 30분 → `EV` + 수동 [금액검증] | `POST /dcrag-to/documents/cost/verify` |
|
||||||
|
|
||||||
|
- 예상 소요 안내는 **30분** (v1.0의 10분은 폐기).
|
||||||
|
- 콜백 수신·스케줄러 전이는 서버. FE는 상세 refetch / draft status 폴링만.
|
||||||
|
- `AC→TE` 성공 시에만 [실무협의서관리]·[검토결과조회] 활성. 실패·타임아웃이면 [실무협의서생성].
|
||||||
|
|
||||||
|
초안 폴링 이벤트 예:
|
||||||
|
|
||||||
|
| `eventType` | `eventMessage` |
|
||||||
|
| --- | --- |
|
||||||
|
| `REQUESTED` | 요청을 접수했습니다. |
|
||||||
|
| `DOWNLOADING` | 신청서를 수신하는 중입니다. |
|
||||||
|
| `ANALYZING` | 자료 준비가 완료되었습니다. |
|
||||||
|
| `GENERATING` | 문서 저장이 완료되었습니다. |
|
||||||
|
| `COMPLETED` | 문서 생성이 완료되었습니다. |
|
||||||
|
| `FAILED` | `errorMessage` 표시 |
|
||||||
|
|
||||||
|
종료: `status === "COMPLETED" | "FAILED"` 또는 `events`에 해당 `eventType`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 2차 결재선 (수동 검토자지정)
|
||||||
|
|
||||||
|
1. `TE`에서 1차 결재선(`elaprCycl = 1`) 완료.
|
||||||
|
2. 금액검증 콜백/Fallback → `AV→EV`.
|
||||||
|
3. 공단 접수자가 `EV`에서 [검토자지정]: 1차 결재선을 시드로 편집 후 `POST /elapr-dmnd` (`elaprCycl: 2`) → `RK`.
|
||||||
|
4. `copy-line` 자동 복사는 사용하지 않는다.
|
||||||
|
|
||||||
|
결재 `process`:
|
||||||
|
|
||||||
|
- 상신·중간 승인: 문서 상태 `TE` 또는 `RK` 유지.
|
||||||
|
- 1차 최종: 서버가 `TE→TP`.
|
||||||
|
- 2차 최종: 서버가 `RK→RR`.
|
||||||
|
- 반려: 직전 결재자 복귀. 문서 상태 유지.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 웹기안기
|
||||||
|
|
||||||
|
- `TE`/`RK` [실무협의서관리] → `webhwp/open` Blob 조회 후 팝업에서 `HwpCtrl.Open(File)`.
|
||||||
|
- [검토완료] 시 HWP 파싱·DB 등재는 **서버**. FE는 결재 `process`만.
|
||||||
|
- 편집본 저장: `POST /pwrk-cnsltn-doc-crt-rslt` multipart. `verSn`은 클라이언트가 보내지 않음.
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
# 레퍼런스 — 화면·버튼 매트릭스
|
||||||
|
|
||||||
|
> 상위: [개발가이드.md](./개발가이드.md)
|
||||||
|
> SoT: `03` §4 화면 및 버튼별 상세 상태전이 명세서 (Row 4~42, 38행)
|
||||||
|
|
||||||
|
구현 위치: `pages/fnrs-cnsltn/lib/button-config.ts` + `ui/components/floating-action-buttons.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 화면 구분
|
||||||
|
|
||||||
|
| 화면 | 소유 | 라우트 (현행) | 비고 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 작성화면 | 기초 `ROLE_USER05` | `/fnrs-cnsltn/new`, `/fnrs-cnsltn/{id}/edit` | `SV` |
|
||||||
|
| 수정화면 | 기초 | `/fnrs-cnsltn/{id}/edit` | `SL` 보완 |
|
||||||
|
| 상세화면 | 광역·환경청·공단·기초(`RB`/`TA`) | `/fnrs-cnsltn/{id}` | 조회 + 액션 버튼 |
|
||||||
|
| 자동화 | AI | 버튼 없음 | `FV`/`AC`/`AV` 진행 패널 |
|
||||||
|
|
||||||
|
공단 상세는 결재선 구분으로 같은 상태라도 버튼이 다르다.
|
||||||
|
|
||||||
|
| `atrzSeComCd` | 역할 |
|
||||||
|
| --- | --- |
|
||||||
|
| `ATRZ01` | 접수자 |
|
||||||
|
| `ATRZ02` | 검토자 |
|
||||||
|
| `ATRZ03` | 승인자 |
|
||||||
|
| `ATRZ99` | 최종승인자 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 38행 매트릭스
|
||||||
|
|
||||||
|
API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일.
|
||||||
|
|
||||||
|
| 순번 | 화면 | 권한 | 결재선 | 상태 | 버튼 | 전이 | 다음 담당 | FE 동작 |
|
||||||
|
| :---: | --- | --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| 1 | 작성 | USER05 | - | 빈값, `SV` | 저장 | `SV` | 본인 | `save-apply` `SV` |
|
||||||
|
| 2 | 작성 | USER05 | - | `SV` | 신청완료 | `ST` | 광역 | `save-apply` `ST` |
|
||||||
|
| 3 | 작성 | USER05 | - | `SV` | 삭제 | 삭제 | — | `DELETE` |
|
||||||
|
| 4 | 상세 | USER04 | - | `ST` | 환경청제출 | `LE` | 환경청 | `updatePrgrsStts` |
|
||||||
|
| 5 | 상세 | USER03 | - | `LE` | 결과회신 | `EP` | 광역 | `tech-review` 자체종결 |
|
||||||
|
| 6 | 상세 | USER03 | - | `LE` | 기술검토 의뢰 | `FV` | 공단 접수 | **`request-examine`** |
|
||||||
|
| 7 | 자동화 | AI | - | `FV` | (파일검증) | `ER` | 공단 접수 | FE 호출 없음. 진행 패널 |
|
||||||
|
| 8 | 상세 | USER01 | 접수자 | `ER` | 파일보완 | `SL` | 기초 | 팝업(파일리스트) → `reqsupplement` |
|
||||||
|
| 9 | 상세 | USER01 | 접수자 | `ER` | 파일검증 | `ER` | 본인 | `dcrag-to/transfer` 재실행 |
|
||||||
|
| 10 | 상세 | USER01 | 접수자 | `ER` | 검토자지정 | `AC` | 다음 결재자 | `POST /elapr-dmnd` |
|
||||||
|
| 11 | 자동화 | AI | - | `AC` | (초안생성) | `TE` | 공단 검토 | FE 호출 없음. 폴링 |
|
||||||
|
| 12 | 상세 | USER01 | 검토자 | `TE` | 실무협의서관리 | 유지 | — | 웹기안기 Open. **초안 성공 시만** |
|
||||||
|
| 13 | 상세 | USER01 | 검토자 | `TE` | 검토결과조회 | 유지 | — | 실무협의 상세 |
|
||||||
|
| 14 | 상세 | USER01 | 검토자 | `TE` | 실무협의서생성 | 유지 | — | **실패/타임아웃/초안 없음** 시 |
|
||||||
|
| 15 | 상세 | USER01 | 검토자 | `TE` | 저장 | `TE` | — | `pwrk save-apply` |
|
||||||
|
| 16 | 상세 | USER01 | 검토자 | `TE` | 검토완료 | `TE` | 다음 결재자 | `elapr process` 상신. 반려 시 직전 결재자 |
|
||||||
|
| 17 | 상세 | USER01 | 검토자 | `TE` | 보완요청 | `SL` | 기초 | 보완 팝업() → `reqsupplement` |
|
||||||
|
| 18 | 상세 | USER01 | 승인자 | `TE` | 결재(승인) | `TE` | 다음 결재자 | `process` |
|
||||||
|
| 19 | 상세 | USER01 | 최종승인자 | `TE` | 결재(회신) | `TP` | 환경청 | `process` 최종 |
|
||||||
|
| 20 | 수정 | USER05 | - | `SL` | 파일보완완료 | `ER` | 공단 접수 | `submitsupplement` (직전=`ER`) |
|
||||||
|
| 21 | 수정 | USER05 | - | `SL` | 보완완료 | `TE` | 공단 검토 | `submitsupplement` (직전=`TE`) |
|
||||||
|
| 22 | 수정 | USER05 | - | `SL` | 보완완료 | `RK` | 공단 검토 | `submitsupplement` (직전=`RK`) |
|
||||||
|
| 23 | 상세 | USER03 | - | `TP` | 재원협의 결과 회신 | `RM` | 광역 | `tech-review` |
|
||||||
|
| 24 | 상세 | USER04 | - | `RM` | 지자체회신 | `RB` | 기초 | `updatePrgrsStts` |
|
||||||
|
| 25 | 상세 | USER05 | - | `RB` | 실무협의서 관리 | 유지 | — | 웹기안기 Open |
|
||||||
|
| 26 | 상세 | USER05 | - | `RB` | 조치완료 | `RA` | 환경청 | **`submit-actn-result` `isSubmit:true`** |
|
||||||
|
| 27 | 상세 | USER04 | - | `RC` | 조치결과서제출 | `RA` | 환경청 | `updatePrgrsStts` |
|
||||||
|
| 28 | 상세 | USER03 | - | `RA` | 실무협의서 관리 | 유지 | — | 웹기안기 Open. **저장 불가** |
|
||||||
|
| 28a | 상세 | USER03 | - | `RA` | 조치결과 검토의뢰 | `AV` | 공단 검토 | **`request-actn-review`** |
|
||||||
|
| 29 | 자동화 | AI | - | `AV` | (금액검증) | `EV` | 공단 접수 | FE 호출 없음. 진행 패널 |
|
||||||
|
| 29a | 상세 | USER01 | 접수자 | `EV` | 검토자지정 | `RK` | 공단 검토 | `POST /elapr-dmnd` cycl=2. 1차 결재선 시드 |
|
||||||
|
| 30 | 상세 | USER01 | 검토자 | `RK` | 실무협의서관리 | 유지 | — | 웹기안기 |
|
||||||
|
| 31 | 상세 | USER01 | 검토자 | `RK` | 금액검증 | `RK` | — | `dcrag-to/cost/verify` |
|
||||||
|
| 32 | 상세 | USER01 | 검토자 | `RK` | 저장 | `RK` | — | `pwrk save-apply` |
|
||||||
|
| 33 | 상세 | USER01 | 검토자 | `RK` | 검토완료 | `RK` | 다음 결재자 | `process` |
|
||||||
|
| 34 | 상세 | USER01 | 승인자 | `RK` | 결재(승인) | `RK` | 다음 결재자 | `process` |
|
||||||
|
| 35 | 상세 | USER01 | 최종승인자 | `RK` | 결재(회신) | `RR` | 환경청 | `process` 최종 |
|
||||||
|
| 36 | 상세 | USER03 | - | `RR` | 실무협의서 관리(조회), 검토결과 회신 | `EP` | 광역 | `tech-review` |
|
||||||
|
| 37 | 작성/상세 | USER04 | - | `EP` | 결과회신, 실무협의서 관리(조회) | `TA` | 기초 | `updatePrgrsStts` |
|
||||||
|
| 38 | 상세 | USER05 | - | `TA` | 결과회신, 실무협의서 관리(조회) | — | — | **API 없음. 조회** |
|
||||||
|
|
||||||
|
`SL` 20~22행: 버튼은 직전 상태에 따라 **하나만** 노출. 파일 루프면 [파일보완완료], 그 외 [보완완료].
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `button-config` 권장 구조
|
||||||
|
|
||||||
|
액션 키는 현행 `FNRS_CNSLTN_VIEW_BUTTON_ACTION`을 확장한다. 신규·이름 변경:
|
||||||
|
|
||||||
|
| 신규/변경 액션 | 라벨 | 비고 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `fileSplmnt` | 파일보완 | `ER` 접수자. 현행에 없음 |
|
||||||
|
| `fileVrfc` | 파일검증 | 유지. `ER` 재실행 |
|
||||||
|
| `dsgntRvwr` | 검토자지정 | `ER→AC`(1차), `EV→RK`(2차) |
|
||||||
|
| `pwrkCnsltnDocMng` | 실무협의서관리 | 유지. `TE`·`RK`·`RB`·`RA`(조회 전용) |
|
||||||
|
| `pwrkCnsltnDocCrt` | 실무협의서생성 | 유지. 실패/타임아웃만 |
|
||||||
|
| `rvwRsltInq` | 검토결과조회 | 신규 |
|
||||||
|
| `actnCmptn` | 조치완료 | `RB→RA`. `isSubmit:true` |
|
||||||
|
| `actnSbmsn` | 조치결과서제출 | 광역 `RC→RA` |
|
||||||
|
| `rqstActnRvw` | 조치결과 검토의뢰 | 환경청 `RA→AV` |
|
||||||
|
| `costVrfc` | 금액검증 | `RK` |
|
||||||
|
| `lclRply` | 지자체회신 | 광역 `RM→RB` |
|
||||||
|
| `fnrsRsltRply` | 재원협의 결과 회신 | 환경청 `TP→RM` |
|
||||||
|
| `rsltRply` | 결과회신 | `LE→EP`, `RR→EP`, `EP→TA`, `TA` 조회 |
|
||||||
|
| `fileSplmntCmptn` | 파일보완완료 | `SL←ER` |
|
||||||
|
| `splmntCmptn` | 보완완료 | `SL←TE\|RK` |
|
||||||
|
|
||||||
|
**삭제**: `fnrsCnsltnAncmnt`(고시→`TB`), `sncnAprv`(인가승인). `EP`/`TA`는 `rsltRply`로 통일.
|
||||||
|
|
||||||
|
### 3-1. 역할 × 상태 (공단 제외)
|
||||||
|
|
||||||
|
```
|
||||||
|
ROLE_USER05
|
||||||
|
"" | SV → 저장, 신청완료, 삭제
|
||||||
|
SL → (직전 ER) 파일보완완료 | (직전 TE|RK) 보완완료
|
||||||
|
RB → 실무협의서 관리, 조치완료
|
||||||
|
TA → 결과회신 (조회), 실무협의서 관리(조회)
|
||||||
|
|
||||||
|
ROLE_USER04
|
||||||
|
ST → 환경청제출 // 현행 반려(rjct→SV)는 SoT 38행에 없음. 유지 시 별도 합의
|
||||||
|
RM → 지자체회신
|
||||||
|
RC → 조치결과서제출
|
||||||
|
EP → 결과회신, 실무협의서 관리(조회)
|
||||||
|
|
||||||
|
ROLE_USER03
|
||||||
|
LE → 결과회신(자체종결), 기술검토 의뢰
|
||||||
|
TP → 재원협의 결과 회신
|
||||||
|
RA → 실무협의서 관리(조회), 조치결과 검토의뢰
|
||||||
|
RR → 실무협의서 관리(조회), 검토결과 회신 (`tech-review` → `EP`)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3-2. 공단 결재선 × 상태
|
||||||
|
|
||||||
|
```
|
||||||
|
ER / ATRZ01 접수자
|
||||||
|
→ 파일보완, 파일검증, 검토자지정
|
||||||
|
|
||||||
|
EV / ATRZ01 접수자
|
||||||
|
→ 검토자지정 (2차 결재선. 1차 결재선 시드, 서식 선택 없음)
|
||||||
|
|
||||||
|
TE / ATRZ02 검토자
|
||||||
|
→ (초안 성공) 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청
|
||||||
|
→ (초안 실패·타임아웃) 실무협의서생성 (+ 위 항목 중 관리/조회는 초안 생긴 뒤)
|
||||||
|
|
||||||
|
TE / ATRZ03 승인자 → 결재(승인)
|
||||||
|
TE / ATRZ99 최종 → 결재(회신)
|
||||||
|
|
||||||
|
RK / ATRZ02 검토자
|
||||||
|
→ (초안 완료) 실무협의서관리
|
||||||
|
→ (초안 완료 + 결재 대기 ATRZ_PRGRS_002, cycl2) 보완요청, 검토완료
|
||||||
|
· 보완요청 UI: TE와 동일 SplmntDmndDialog
|
||||||
|
RK / ATRZ03 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(승인)
|
||||||
|
RK / ATRZ99 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(회신)
|
||||||
|
· 결재 UI: ElaprApprovalDialog (elaprCycl=2) 승인/반려 → PUT process
|
||||||
|
```
|
||||||
|
|
||||||
|
`FV`, `AC`, `AV`: 액션 빈 배열 + 진행 패널.
|
||||||
|
|
||||||
|
`PENDING_APPROVAL_ACTIONS`: `dmndSplmnt`, `rvwCmptn`, `atrzAprv`, `atrzRply`만.
|
||||||
|
`pwrkCnsltnDocMng`는 결재 대기와 무관(초안 완료만). ATRZ02·ATRZ03·ATRZ99라도 pending 없으면 관리만 노출.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 초안 성공 vs 실패 버튼
|
||||||
|
|
||||||
|
`TE`에서 `crtDmndList[].crtSttsComCd` / 폴링 `status`로 분기.
|
||||||
|
|
||||||
|
| 조건 | 노출 |
|
||||||
|
| --- | --- |
|
||||||
|
| `C` / `COMPLETED` | 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청 |
|
||||||
|
| `F` / `FAILED` 또는 30분 Fallback 후 초안 없음 | 실무협의서생성 |
|
||||||
|
| `R`/`I` 생성 중 | 진행 패널. 위 버튼 비활성 (`AC`와 동일 UX, 상태가 이미 `TE`면 패널+생성 버튼) |
|
||||||
|
|
||||||
|
`RK`는 금액검증 성공이 기본. Fallback 후 [금액검증] 재실행만 추가 노출.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 현행 `button-config.ts`와의 차이
|
||||||
|
|
||||||
|
| 항목 | 현재 | To-Be |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| USER05 | `""/SV/TA(고시)` | `SL`, `RB` 추가. `TA`는 조회 |
|
||||||
|
| USER04 | `ST`(제출+반려), `EP`(인가승인) | `RM/RC/EP` 추가. 인가승인 삭제 |
|
||||||
|
| USER03 | `LE`, `TP`(→EP로 회신) | `RA`, `RR` 추가. `TP`는 `RM` |
|
||||||
|
| USER01 | `ER`(파일검증, 검토자지정), `TE` | `ER`에 파일보완. `RK` 전면 추가 |
|
||||||
|
| USER01 ATRZ | `ER`, `TE`만 `needsRoleUser01AtrzResolve` | `RK` 포함 |
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,674 @@
|
|||||||
|
# dmbpRegist.jsp → React 마이그레이션 분석·구현 가이드
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ----------- | ------------------------------------------------------ |
|
||||||
|
| 원본 JSP | `adssNewOld/.../fnrsc/dmbp/dmbpRegist.jsp` |
|
||||||
|
| 대상 앱 | `frontend/apps/as` |
|
||||||
|
| 도메인 | 하수도기본계획 (구: 기본계획검토 / dmbp) |
|
||||||
|
| API 엔티티 | `@/entities/sewer-bsc-plan` (`SewerBscPlanController`) |
|
||||||
|
| 참조 패턴 | `@/pages/fnrs-cnsltn` + `@/widgets/pbadms-sprt` |
|
||||||
|
| 문서 목적 | contents 영역 **섹션별** UI·상태·API 매핑 및 구현 방향 |
|
||||||
|
| 작성 기준일 | 2026-07-27 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 마이그레이션 범위
|
||||||
|
|
||||||
|
### 1-1. 포함 (contents만)
|
||||||
|
|
||||||
|
JSP `568~1274`행: `<div class="contents">` ~ `<!-- //contents -->`
|
||||||
|
|
||||||
|
| 영역 | JSP 식별 | 개요 |
|
||||||
|
| ---- | ---------------------------------- | --------------------------------------------------------------- |
|
||||||
|
| A | `gTitle1` | 페이지 타이틀·브레드크럼 |
|
||||||
|
| B | `slide_10` | 기본계획변경 신청(마스터·연도·개요) |
|
||||||
|
| C | `slide_15` | 신청자·설계자 정보 |
|
||||||
|
| D | `slide_19` | 개인정보 동의 |
|
||||||
|
| E | `slide_11` | 사업개요(단계별 인구·보급률) |
|
||||||
|
| F | `slide_12` | 총사업비 — `BizCstMatrixFormSection` 전용 |
|
||||||
|
| G | `slide_13` | 재원분담 |
|
||||||
|
| H | `slide_14` | 처리시설별 하수도정비기본계획 (Checkbox + Tabs) |
|
||||||
|
| I | `slide_16` | 체크리스트 + 필수첨부 + 기타첨부 |
|
||||||
|
| J | JSP 상단 `saveBtn` / `presentnBtn` | React: 폼 하단 액션바 (`fnrs-form-page` 279~323과 동일 구성, API만 sewer) |
|
||||||
|
|
||||||
|
### 1-2. 제외
|
||||||
|
|
||||||
|
- `<%@ include %>`: top / sideMenu / footer / `bscPlnPopup.jsp` / `sndrdBzAmnt.jsp`
|
||||||
|
- 레이아웃·메뉴·권한 셸 → 기존 `BaseLayout` / `PageLayout` / AUTH 체계
|
||||||
|
- `mStep1` 진행 단계 표시 (미구현 · 마이그레이션 범위 밖)
|
||||||
|
|
||||||
|
팝업(`이전기본계획조회`)은 **별도 Dialog 컴포넌트**로 재구현하며, include JSP 자체는 포팅하지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 목표 아키텍처
|
||||||
|
|
||||||
|
```
|
||||||
|
app/(with-base-layout)/sewer-bsc-plan/regist/page.tsx
|
||||||
|
└─ pages/sewer-bsc-plan/ui/sewer-bsc-plan-regist-page.tsx
|
||||||
|
└─ sewer-bsc-plan-form-page.tsx # FormProvider + 섹션 조립
|
||||||
|
├─ sections/*-form-section.tsx # 도메인 전용
|
||||||
|
└─ widgets/pbadms-sprt/* # 공통 재사용
|
||||||
|
|
||||||
|
entities/sewer-bsc-plan/ # 이미 구현됨
|
||||||
|
saveApply / findDetails / submitSupplement …
|
||||||
|
```
|
||||||
|
|
||||||
|
**폼 스택 (fnrs-cnsltn과 동일)**
|
||||||
|
|
||||||
|
- `react-hook-form` + `zod` (`zodResolver`, `mode: "onChange"`)
|
||||||
|
- 섹션 래퍼: `SectionCollapsible` (Accordion 아님)
|
||||||
|
- UI: `@common/components` (Input, Select, Textarea, Button, Radio, Tabs, Table 등)
|
||||||
|
- 그리드가 필요한 곳만 `@tanstack/react-table` + `DataTable`
|
||||||
|
- 데이터: React Query (`useSaveApplySewerBscPlan` 등) — 목데이터는 API 미연동 구간만 `@faker-js/faker`
|
||||||
|
|
||||||
|
**Vercel React Best Practices (적용 요약)**
|
||||||
|
|
||||||
|
- `client-swr-dedup`: React Query로 체크리스트/필수첨부/코드 조회 중복 제거
|
||||||
|
- `bundle-dynamic-imports`: 처리시설 Tabs 패널·이전계획 Dialog는 `next/dynamic` 후보
|
||||||
|
- `rerender-derived-state`: 단계 연도·합계·진행률은 render 중 파생, 불필요 effect 금지
|
||||||
|
- `async-parallel`: 등록 진입 시 코드/체크리스트트리/필수첨부트리를 병렬 조회
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 전체 레이아웃
|
||||||
|
|
||||||
|
`fnrs-form-page.tsx`와 동일한 **FormProvider + 세로 섹션 스택** 구조를 따르고,
|
||||||
|
하단 액션바는 **[`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx)** 과 동일 구성(목록·저장·신청완료·삭제·보완완료)이며 **API/라우트만 sewer** 로 바꾼다.
|
||||||
|
타이틀·브레드크럼은 폼 밖(`PageLayout` / regist·edit·view 셸)에서 처리한다.
|
||||||
|
(상세 View의 sticky FAB·검토/결재 버튼은 본 등록 문서 범위 밖 — `fnrs-view-page` 대응 후속.)
|
||||||
|
|
||||||
|
### 3-1. 컴포넌트 트리 (fnrs 대응)
|
||||||
|
|
||||||
|
```
|
||||||
|
PageLayout (title / breadcrumb)
|
||||||
|
└─ SewerBscPlanRegistPage | SewerBscPlanEditPage | SewerBscPlanViewPage
|
||||||
|
└─ SewerBscPlanFormPage | SewerBscPlanViewPage 본문
|
||||||
|
FormProvider (등록/수정) / 읽기 섹션 (상세)
|
||||||
|
└─ <form|div className="mt-6 space-y-4">
|
||||||
|
├─ BasicInfoFormSection // JSP slide_10
|
||||||
|
├─ ContactInfoFormSection // JSP slide_15 (pbadms-sprt)
|
||||||
|
├─ PrivacyConsentSection // JSP slide_19 (pbadms-sprt, 등록·수정)
|
||||||
|
├─ OutlineFormSection // JSP slide_11 (사업개요, 신규)
|
||||||
|
├─ BizCstMatrixFormSection // JSP slide_12 (총사업비 전용, 신규)
|
||||||
|
├─ FnrsCstFormSection // JSP slide_13 (재원분담, 신규 ※ FnrsShar 아님)
|
||||||
|
├─ TmplntFormSection // JSP slide_14 (처리시설, Tabs로 시설별 패널)
|
||||||
|
├─ ChecklistFormSection // JSP slide_16 (pbadms-sprt)
|
||||||
|
├─ AttachmentsFormSection // JSP 필수·기타첨부 (pbadms-sprt)
|
||||||
|
├─ SplmntDmndFormSection // edit + SL 상태만 (pbadms-sprt)
|
||||||
|
└─ form footer action bar // fnrs-form-page 279~323 동일
|
||||||
|
// [목록] / [저장] / edit시 [신청완료][삭제] / SL시 [보완완료]
|
||||||
|
```
|
||||||
|
|
||||||
|
**폼 하단 액션바 버튼 구성**
|
||||||
|
기준: [`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx). UI·분기만 동일하고 훅/라우트는 sewer.
|
||||||
|
|
||||||
|
| 순서 | 버튼 | 조건 | sewer 비고 |
|
||||||
|
| ---- | -------- | ----------------------------------------- | ---------------------------------------------------- |
|
||||||
|
| 1 | 목록 | 항상 | `router.push("/sewer-bsc-plan")` |
|
||||||
|
| 2 | 저장 | `!isSplmntPrgrsStts` | `type="submit"` → `saveApply` / 임시저장(SV) |
|
||||||
|
| 3 | 신청완료 | `!isSplmntPrgrsStts && isEditMode` | 체크리스트+필수첨부 검증 → `saveApply` / LE |
|
||||||
|
| 4 | 삭제 | `!isSplmntPrgrsStts && isEditMode` | confirm → `delete` → 목록 |
|
||||||
|
| 5 | 보완완료 | `isSplmntPrgrsStts` (`prgrsSttsComCd=SL`) | `submitSupplement` (fnrs `onSplmntCmptn` 대응) |
|
||||||
|
|
||||||
|
**fnrs와의 차이 (섹션 치환만, 골격 동일)**
|
||||||
|
|
||||||
|
| fnrs-form-page | sewer-bsc-plan |
|
||||||
|
| ---------------------------------------------- | ------------------------------------------------------ |
|
||||||
|
| `BasicInfoFormSection` | 동일 역할 (마스터·연도·개요 필드는 sewer DTO) |
|
||||||
|
| `ContactInfoFormSection` | 동일 (`fieldPrefix="sewerBscPlanDtl"`) |
|
||||||
|
| `PrivacyConsentSection` | 동일 |
|
||||||
|
| `BusinessScaleFormSection` | → `OutlineFormSection` (단계별 인구·보급률) |
|
||||||
|
| `BusinessCostFormSection` | → `BizCstMatrixFormSection` (단계×원가 계층 전용) |
|
||||||
|
| `FnrsSharFormSection` | → `FnrsCstFormSection` (국고·지방비·원인자·기타) |
|
||||||
|
| _(없음)_ | `TmplntFormSection` (체크박스 + Tabs 시설별 매트릭스) |
|
||||||
|
| `ChecklistFormSection` `docSeComCd="FNRS"` | `docSeComCd="SEWER"` (TBD) |
|
||||||
|
| `AttachmentsFormSection` | 동일 패턴 |
|
||||||
|
| `SplmntDmndFormSection` | 동일 (`readOnly={!isSplmntPrgrsStts}`) |
|
||||||
|
| 폼 하단 액션바 (목록·저장·신청완료·삭제·보완완료) | **동일 구성, API·라우트만 sewer** |
|
||||||
|
|
||||||
|
### 3-2. 본문 ASCII (폼 페이지)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ PageLayout: 하수도기본계획 신청/수정 breadcrumb │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ FormProvider │
|
||||||
|
│ <form className="mt-6 space-y-4"> │
|
||||||
|
│ │
|
||||||
|
│ ┌─ SectionCollapsible: 기본계획 신청 ─────────────────────┐ │
|
||||||
|
│ │ BasicInfoFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 신청자 정보 ────────────────────────────────────────────┐ │
|
||||||
|
│ │ ContactInfoFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 개인정보 동의 ──────────────────────────────────────────┐ │
|
||||||
|
│ │ PrivacyConsentSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 사업개요 ────────────── [이전기본계획조회] ─────────────┐ │
|
||||||
|
│ │ OutlineFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 총사업비 (백만원) ──────────────────────────────────────┐ │
|
||||||
|
│ │ BizCstMatrixFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 재원분담 (백만원) ──────────────────────────────────────┐ │
|
||||||
|
│ │ FnrsCstFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 처리시설별 하수도정비기본계획 ──────────────────────────┐ │
|
||||||
|
│ │ TmplntFormSection │ │
|
||||||
|
│ │ ├─ BscFctList Checkbox │ │
|
||||||
|
│ │ └─ Tabs (선택 시설별 TabsTrigger / TabsContent) │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 체크리스트 ─────────────────────────────────────────────┐ │
|
||||||
|
│ │ ChecklistFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 첨부파일 ───────────────────────────────────────────────┐ │
|
||||||
|
│ │ AttachmentsFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ┌─ 보완요구 (edit + SL) ───────────────────────────────────┐ │
|
||||||
|
│ │ SplmntDmndFormSection │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ ┌─ form footer (fnrs-form-page 279~323 동일) ──────────────┐ │
|
||||||
|
│ │ [목록] [저장] / edit: [신청완료][삭제] / SL: [보완완료] │ │
|
||||||
|
│ │ ※ API·라우트만 sewer — UI 분기는 fnrs와 동일 │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────┘ │
|
||||||
|
│ </form> │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3-3. 액션바 동작 (`fnrs-form-page` 279~323 동일, API만 sewer)
|
||||||
|
|
||||||
|
구현 위치: `sewer-bsc-plan-form-page.tsx` 하단
|
||||||
|
(`fnrs-form-page.tsx`의 `mx-4 flex flex-wrap justify-center gap-3` 블록을 그대로 옮기고 훅·path만 교체)
|
||||||
|
|
||||||
|
| 버튼 | 조건 | 대표 동작 |
|
||||||
|
| -------- | ---------------------------- | -------------------------------------------------------------------------- |
|
||||||
|
| 목록 | 항상 | `router.push("/sewer-bsc-plan")` |
|
||||||
|
| 저장 | `!isSplmntPrgrsStts` | `type="submit"` → `saveApply` + 임시저장(`SV`) |
|
||||||
|
| 신청완료 | edit && `!isSplmntPrgrsStts` | 체크리스트 완료 + **필수 첨부 완료** 검증 → `saveApply` + `LE` |
|
||||||
|
| 삭제 | edit && `!isSplmntPrgrsStts` | confirm → `delete` → 목록 |
|
||||||
|
| 보완완료 | `isSplmntPrgrsStts` (SL) | `form.handleSubmit` → `useSubmitSupplementSewerBscPlan` |
|
||||||
|
|
||||||
|
**신청완료 사전 검증** (fnrs `handleApplyClick`과 동일 정책)
|
||||||
|
|
||||||
|
1. **체크리스트**: `isFormChecklistComplete(cklstResultList)` — 미완료 시 `"모든 체크리스트 항목을 체크해야 신청이 가능합니다."`
|
||||||
|
2. **필수 첨부**: `esntlAtchYn === "Y"`인 필수첨부 슬롯마다 `esntlAtchFileList`에 `atchFileGroupId`가 있어야 함
|
||||||
|
- Form: `AttachmentsFormSection`의 트리 + 폼 `esntlAtchFileList`
|
||||||
|
- 미충족 시 예: `"필수 첨부파일을 모두 등록해야 신청이 가능합니다."`
|
||||||
|
- 헬퍼 후보: `isFormEsntlAtchComplete(requiredTree, esntlAtchFileList)` (`pbadms-sprt`에 추가)
|
||||||
|
|
||||||
|
저장에는 위 검증을 적용하지 않는다 (임시저장 허용).
|
||||||
|
|
||||||
|
저장/제출 버튼을 섹션 헤더(`gTitle2 gRt`)에 두지 않는다 — **폼 하단 액션바 단일 진입점** (§4-J).
|
||||||
|
|
||||||
|
### 3-4. 라우트
|
||||||
|
|
||||||
|
| 화면 | path | 셸 → FormPage |
|
||||||
|
| ---- | --------------------------- | ------------------------------------------ |
|
||||||
|
| 등록 | `/sewer-bsc-plan/regist` | `mode="create"` |
|
||||||
|
| 수정 | `/sewer-bsc-plan/[id]/edit` | `mode="edit"` + `useSewerBscPlanDetails` |
|
||||||
|
| 상세 | `/sewer-bsc-plan/[id]` | ViewPage (본 문서 범위 밖, fnrs-view 대응) |
|
||||||
|
|
||||||
|
`docSeComCd` 후보: 백엔드 체크리스트·필수첨부 등록값 확인 후 **`SEWER`** (또는 레거시 `DMBP`) — **TBD, CMS 코드와 맞출 것**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 섹션별 상세 구현
|
||||||
|
|
||||||
|
### 4-0. Hidden / 세션 컨텍스트 (폼 루트)
|
||||||
|
|
||||||
|
JSP hidden → React 폼 기본값·세션 스토어로 흡수.
|
||||||
|
|
||||||
|
| JSP | 의미 | React |
|
||||||
|
| ------------------------------- | --------------- | ------------------------------------------------------- |
|
||||||
|
| `chgSe` / `chgSeSp` / `chgSeCd` | 변경구분 | `sewerBscPlan.sewerBscPlanSeComCd` + 표시용 코드명 |
|
||||||
|
| `progrsSttus` | SAVE / PRESENTN | 저장·제출 시 `prgrsSttsComCd`로 매핑 (임시저장 vs 제출) |
|
||||||
|
| `planSeCd` | 고정 `A` | 필요 시 상수 또는 API 필드 확인 |
|
||||||
|
| `adstDstrCd` / `rgnCd` | 행정구역 | `sewerBscPlan.admdstCd` |
|
||||||
|
| `sidoNm` / `gugunNm` | 표시용 | 읽기 전용 표시 (`detDto.sido`/`gugun`) |
|
||||||
|
|
||||||
|
구현: `getCreateDefaultValues(userInfo)`에서 로그인 사용자·소속 지자체 주입.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-A. 타이틀·브레드크럼
|
||||||
|
|
||||||
|
| JSP | React |
|
||||||
|
| ------------------------ | ------------------------------------------------------ |
|
||||||
|
| `h3` 기본계획검토 | PageLayout title: **하수도기본계획 신청** (IIPBS 명명) |
|
||||||
|
| 설치인가 및 제원협의 > … | 메뉴 메타 기반 breadcrumb |
|
||||||
|
|
||||||
|
별도 섹션 컴포넌트 불필요. 라우트 `page.tsx` / layout metadata로 처리.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-B. 기본계획변경 신청 (`slide_10`)
|
||||||
|
|
||||||
|
**파일**: `ui/sections/basic-info-form-section.tsx`
|
||||||
|
**래퍼**: `SectionCollapsible` title=`기본계획 신청`
|
||||||
|
**필드 접두사**: `sewerBscPlan.*` + `sewerBscPlanDtl.*`
|
||||||
|
|
||||||
|
| 표시명 | JSP | API 매핑 | UI | 필수 | 비고 |
|
||||||
|
| ---------------- | ----------------- | ------------------------------- | ------------------ | ---- | ---------------------------------- |
|
||||||
|
| 기본계획명 | `drngMtncPlanNm` | `sewerBscPlan.sewerBscPlanNm` | Input | Y | |
|
||||||
|
| 계획년도 | `planYr` | `sewerBscPlanDtl.planYr` | Select (현재~2005) | Y | |
|
||||||
|
| 기준년도 | `crtrYr` | `sewerBscPlanDtl.crtrYr` | Select | Y | |
|
||||||
|
| 1~4단계 목표연도 | `goalYrStep1~4` | `frlvlGoalYr`~`folvlGoalYr` | Select (현재~+30) | Y | 1단계 변경 시 +5/+10/+15 자동 연동 |
|
||||||
|
| 계획개요 | `bzCn` | `sewerBscPlanDtl.bizOtlnCn` | Textarea | Y | |
|
||||||
|
| 구분 | `#chgSeNm` | `sewerBscPlanSeComCd` (+코드명) | 읽기 또는 Select | | 신규/변경 등 |
|
||||||
|
| 신청 지자체 | `${lclgvrnmntNm}` | `admdstCd` | 읽기 전용 | | |
|
||||||
|
|
||||||
|
**동작**
|
||||||
|
|
||||||
|
- `goalYrStep1` onChange → 2·3·4단계 기본값 세팅 + E/F/G 헤더 연도 span 갱신 (`watch` 파생)
|
||||||
|
- JSP 상단 `gRt` 저장/제출은 사용하지 않음 → FormPage 하단 액션바 (§3-3, §4-J)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-C. 신청자 정보 (`slide_15`)
|
||||||
|
|
||||||
|
**재사용**: `@/widgets/pbadms-sprt`의 `ContactInfoFormSection`
|
||||||
|
**props**: `fieldPrefix="sewerBscPlanDtl"` (또는 dtl 필드 구조에 맞게)
|
||||||
|
|
||||||
|
| 표시 | JSP | API |
|
||||||
|
| ---------------------------- | --------------------------- | --------------------------------------------------------------------------------------- |
|
||||||
|
| 신청자 이름/전화/부서/이메일 | `${UsrNm}` 등 서버렌더 | 로그인 사용자 읽기 전용 → `aplcntNm`, `aplcntMblTelno`, `aplcntOgdpNm`, `aplcntEmlAddr` |
|
||||||
|
| 설계자 | `dsgnrNm/Telno/Psitn/Email` | `dsgnrNm`, `dsgnrTelno`, `dsgnrOgdpNm`, `dsgnrEmlAddr` |
|
||||||
|
|
||||||
|
신청자 4필드는 세션에서 defaultValues에 채우고 Input `disabled`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-D. 개인정보 동의 (`slide_19`)
|
||||||
|
|
||||||
|
**재사용**: `PrivacyConsentSection`
|
||||||
|
|
||||||
|
| JSP | React |
|
||||||
|
| ----------------------------- | ------------------------------------------------------------------ |
|
||||||
|
| 고지 textarea + 보유기간 문구 | 동일 문구 상수/마크다운 |
|
||||||
|
| `privacyAgree` Y/N | 폼 필드 `privacyAgree: "Y" \| "N"` (API 미전송, 클라이언트 검증만) |
|
||||||
|
|
||||||
|
**검증**: 제출 시 `Y` 필수 (JSP `presentnBtn`과 동일). 저장(임시)은 동의 없이 허용할지 정책 TBD — 기본은 JSP와 같이 **제출만 강제**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-E. 사업개요 (`slide_11`)
|
||||||
|
|
||||||
|
**파일**: `ui/sections/outline-form-section.tsx` (신규, 도메인 전용)
|
||||||
|
**API**: `sewerBscPlanOtlnList: SewerBscPlanOtlnItem[]` (planStpNo 1~4)
|
||||||
|
|
||||||
|
| 행(구분) | JSP name | Item 필드 |
|
||||||
|
| ------------ | --------------- | ------------------- |
|
||||||
|
| 계획인구 | `arrPlanPpltn` | `sewerPlanPpltnCnt` |
|
||||||
|
| 처리인구 | `arrPrcsPpltn` | `sewerPrcsPpltnCnt` |
|
||||||
|
| 하수도보급율 | `arrDrngSplyRt` | `sewerPsplyRt` |
|
||||||
|
|
||||||
|
열: 1~4단계 (헤더에 `frlvlGoalYr` 등 표시).
|
||||||
|
|
||||||
|
**버튼**: `이전기본계획조회` → Dialog (`PreviousPlanLookupDialog`)
|
||||||
|
|
||||||
|
- 레거시: `fnbscPlnPopupToggle` + `bscPlnPopup.jsp`
|
||||||
|
- React: Modal + 조회 API (**TBD**: 이전 계획 조회 엔드포인트; 없으면 1차는 Dialog UI만 + mock)
|
||||||
|
|
||||||
|
숫자 입력: 콤마 제거 후 number 전송 (제출 시 JSP `replaceComma` 대체).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-F. 총사업비 (`slide_12`)
|
||||||
|
|
||||||
|
**파일**: `ui/sections/biz-cst-matrix-form-section.tsx` (**전용 컴포넌트** — fnrs `BusinessCostFormSection` 재사용 금지)
|
||||||
|
**래퍼**: `SectionCollapsible` title=`총사업비` (단위 `백만원`은 헤더 우측)
|
||||||
|
|
||||||
|
**JSP 현실**: 단계(1~4) × 원가 계층(총사업비, 공사비, 처리시설, 하수관로 신설/보수, 펌프장, 배수설비, 기타, 부대경비, 설계/감리/부대비). 일부 readonly·자동합산 JS.
|
||||||
|
|
||||||
|
**신규 API**: `bizCstList: SewerBscPlanBizCstItem[]` = `{ planStpNo, acntgSeCd, cstrnAmt }`
|
||||||
|
|
||||||
|
**구현 방향 (`BizCstMatrixFormSection` 전용)**
|
||||||
|
|
||||||
|
- 행 = `useAcntgSeMngTree` → `buildAcntgCostItems` (fnrs `BusinessCostFormSection`과 동일 출처)
|
||||||
|
- `costItems` 로드 시 `useEffect`로 `bizCstList`를 `mergeBizCstList(existing, costItems)`로 동기화
|
||||||
|
- 열 = `planStpNo` 1~4 (헤더에 목표연도 표시)
|
||||||
|
- readonly 행(`CostItemDef.readonly=true`)은 children 합산으로 자동계산; Input에 `readOnly` + `bg-muted`
|
||||||
|
- 변경 시 `setAmt` → `recalculateBizCstMatrix(list, costItems)` → `setValue("bizCstList")`
|
||||||
|
- 저장 시 섹션 onChange 합산을 신뢰하여 `bizCstList` 그대로 전송 (`toBizCstSaveList` identity)
|
||||||
|
|
||||||
|
**표준사업비 조회** 버튼은 JSP에서 주석 처리됨 → **미구현**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-G. 재원분담 (`slide_13`)
|
||||||
|
|
||||||
|
**주의**: fnrs의 `FnrsSharFormSection`(재원협의 공유구분 트리)과 **다름**.
|
||||||
|
|
||||||
|
| 행 | JSP | `SewerBscPlanFnrsCstItem` |
|
||||||
|
| ------------ | -------------- | ------------------------- |
|
||||||
|
| 총사업비 | `arrTotAmnt` | `sumAmt` (또는 파생) |
|
||||||
|
| 국고 | `arrNlepAmt` | `gvexpAmt` |
|
||||||
|
| 지방비 | `arrLcepAmt` | `lcexpAmt` |
|
||||||
|
| 원인자부담금 | `arrCusrAlotm` | `orgprBdchgAmt` |
|
||||||
|
| 기타 | `arrEtcAmt` | `etcAmt` |
|
||||||
|
|
||||||
|
**파일**: `ui/sections/fnrs-cst-form-section.tsx` (신규)
|
||||||
|
데이터: `fnrsCstList` 길이 4 (`planStpNo` 1~4).
|
||||||
|
|
||||||
|
검증(선택): 단계별 `gvexp+lcexp+orgpr+etc === sumAmt` (JSP `calculateTotalFunding` 참고).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-H. 처리시설별 하수도정비기본계획 (`slide_14`) — 핵심 복잡도
|
||||||
|
|
||||||
|
**JSP 동작**
|
||||||
|
|
||||||
|
1. `BscFctList` 체크박스로 처리시설 다중 선택 (`arrBscFctCd`)
|
||||||
|
2. 선택 시 `createFormSection(code, name)`로 **당초 4단계 | 현재 | 변경 4단계** 매트릭스 동적 삽입
|
||||||
|
→ **React**: 세로 삽입 대신 `@common/components` `Tabs`로 시설별 패널 관리 (§아래 Tabs 규칙)
|
||||||
|
3. 활성 `formFields`(축소판): 처리구역면적, 하수처리인구, 계획하수량, 시설용량, 분류식 오수/우수, 합류식, 차집, 배수설비, 오수중계/일반/우수 펌프
|
||||||
|
4. 시설별 필수첨부 `createFileAttachmentSection`
|
||||||
|
5. `fetchBasicPlanData`로 당초(이전) 데이터 바인딩
|
||||||
|
|
||||||
|
**API 매핑**
|
||||||
|
|
||||||
|
| UI | DTO |
|
||||||
|
| -------------------- | ------------------------------------------------------------- |
|
||||||
|
| 선택 시설 목록 | `sewerBscPlanTmplntList[].swtrmtFcltCd` (+ `atchFileGroupId`) |
|
||||||
|
| 단계별 수치 (변경측) | `sewerBscPlanTmplntDtlList[]` → `SewerBscPlanTmplntDtlItem` |
|
||||||
|
| 임시저장 | `tmplntTmprStrgList` (편집 중 임시, 필요 시) |
|
||||||
|
|
||||||
|
필드 대응 (변경 단계 기준):
|
||||||
|
|
||||||
|
| UI 라벨 | `SewerBscPlanTmplntDtlItem` |
|
||||||
|
| -------------- | --------------------------- |
|
||||||
|
| 처리구역면적 | `prcsZoneArea` |
|
||||||
|
| 하수처리인구 | `swtrmtPpltnCnt` |
|
||||||
|
| 계획하수량 | `planSwqty` |
|
||||||
|
| 시설용량 | `fcltCpct` |
|
||||||
|
| 분류식 오수 | `sprsysDrtwPplLen` |
|
||||||
|
| 분류식 우수 | `sprsysExlnPplLen` |
|
||||||
|
| 합류식 | `cbsysPplLen` |
|
||||||
|
| 차집관로 | `icptgPplLen` |
|
||||||
|
| 배수설비 | `drgCnt` |
|
||||||
|
| 오수 중계 펌프 | `drtwRlyPumpCnt` |
|
||||||
|
| 오수 일반 펌프 | `drtwPumpCnt` |
|
||||||
|
| 우수 펌프 | `exlnPumpCnt` |
|
||||||
|
|
||||||
|
**구현 구성**
|
||||||
|
|
||||||
|
```
|
||||||
|
ui/sections/tmplnt-form-section.tsx
|
||||||
|
├─ TmplntFacilityPicker # BscFctList Checkbox 목록 (시설 마스터 API TBD)
|
||||||
|
└─ Tabs (@common/components Tabs) # 선택 시설별 동적 패널
|
||||||
|
├─ TabsList / TabsTrigger # value=swtrmtFcltCd, 라벨=시설명
|
||||||
|
└─ TabsContent (시설 1개당)
|
||||||
|
├─ TmplntMatrixTable # 당초|현재|변경 매트릭스 (useFieldArray)
|
||||||
|
└─ TmplntFacilityAttachments # 시설별 필수첨부 (선택)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tabs 관리 규칙** (`packages/common/components/src/ui/tabs`)
|
||||||
|
|
||||||
|
- `BscFctList` 체크 **선택** → `useFieldArray` append + 해당 시설 `TabsTrigger`/`TabsContent` 추가 → `Tabs` `value`를 신규 시설로 전환
|
||||||
|
- 체크 **해제** → confirm 후 remove + 해당 탭 제거 → 남은 탭 중 하나로 `value` 이동 (없으면 Tabs 영역 비표시)
|
||||||
|
- 선택 시설이 0개면 Tabs UI 숨김; 1개 이상이면 Tabs로 시설별 패널 전환
|
||||||
|
- 매트릭스·첨부는 각 `TabsContent` 안에만 렌더 (JSP의 세로 동적 삽입을 탭 패널로 대체)
|
||||||
|
|
||||||
|
**매트릭스 열**
|
||||||
|
|
||||||
|
- **당초 열**: 이전계획 API 조회 결과 읽기 전용 (없으면 빈 칸)
|
||||||
|
- **현재 열**: 기준년도 시점 값 (API 제공 시)
|
||||||
|
- **변경 열**: 편집 가능 → 저장 payload의 dtl만 전송
|
||||||
|
|
||||||
|
시설 마스터 목록 API: 레거시 `BscFctList` 대체 — **TBD** (`swtrmtFclt` 계열).
|
||||||
|
|
||||||
|
`bundle-dynamic-imports`: `TmplntFormSection`(또는 TabsContent 내부 매트릭스)을 `next/dynamic` 후보.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-I. 체크리스트 + 첨부 (`slide_16`)
|
||||||
|
|
||||||
|
#### I-1. 체크리스트
|
||||||
|
|
||||||
|
**재사용**: `ChecklistFormSection`
|
||||||
|
`docSeComCd="<SEWER|DMBP TBD>"`
|
||||||
|
`fieldPrefix`: 마스터에서 `taskSeComCd`를 읽는 경로 (예: `sewerBscPlan`)
|
||||||
|
|
||||||
|
| JSP | React |
|
||||||
|
| ----------------------- | ------------------------------------------- |
|
||||||
|
| 업무탭 `ceckEssntlArr` | 코드 `taskSeComCd` 다중 → Tabs |
|
||||||
|
| 진행률 / 전체 예·아니오 | 위젯 내장 로직 |
|
||||||
|
| `exmntResult` Y/N | `cklstResultList[].selected` (+ `picRvwCn`) |
|
||||||
|
|
||||||
|
저장: `cklstResultList: CklstResultItem[]`
|
||||||
|
|
||||||
|
#### I-2. 필수 첨부 (탭별)
|
||||||
|
|
||||||
|
**재사용**: `AttachmentsFormSection`
|
||||||
|
`docSeComCd` 동일, `esntlAtchFileList`
|
||||||
|
|
||||||
|
레거시: 체크리스트 탭마다 `essntlList` + `uploadFile` multipart.
|
||||||
|
신규: TUS/`attach-service` + `esntlAtchFileId` / `atchFileGroupId` (fnrs와 동일).
|
||||||
|
|
||||||
|
#### I-3. 기타 첨부
|
||||||
|
|
||||||
|
`AttachmentsFormSection`의 etc 영역 또는 `atchFileGroupId` on `sewerBscPlanDtl`.
|
||||||
|
JSP `uploadFileETC` multiple → Zip 안내 문구 유지.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-J. 저장 / 제출
|
||||||
|
|
||||||
|
**UI 구성**: [`fnrs-form-page.tsx` 279~323](../../src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx) 과 **동일**.
|
||||||
|
마크업·분기(`isSplmntPrgrsStts` / `isEditMode`)·버튼 색상·`disabled={isSubmitting}` 패턴을 그대로 두고, **호출 API·목록 path만 sewer** 로 교체한다.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="mx-4 flex flex-wrap justify-center gap-3 sm:gap-4">
|
||||||
|
<Button color="white" type="button" onClick={() => router.push("/sewer-bsc-plan")} disabled={isSubmitting}>
|
||||||
|
목록
|
||||||
|
</Button>
|
||||||
|
{!isSplmntPrgrsStts ? (
|
||||||
|
<>
|
||||||
|
<Button type="submit" disabled={isSubmitting}>저장</Button>
|
||||||
|
{isEditMode && (
|
||||||
|
<>
|
||||||
|
<Button color="green" type="button" onClick={handleApplyClick} disabled={isSubmitting}>
|
||||||
|
신청완료
|
||||||
|
</Button>
|
||||||
|
<Button color="red" type="button" onClick={handleDelClick} disabled={isSubmitting}>
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button type="button" onClick={form.handleSubmit(onSplmntCmptn)} disabled={isSubmitting}>
|
||||||
|
보완완료
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
| 버튼 | fnrs 대응 | sewer API / 동작 |
|
||||||
|
| -------- | ----------------------------- | ------------------------------------------------------------ |
|
||||||
|
| 목록 | `router.push("/fnrs-cnsltn")` | `router.push("/sewer-bsc-plan")` |
|
||||||
|
| 저장 | `saveApply` + 임시저장 | `useSaveApplySewerBscPlan` + `prgrsSttsComCd`=임시저장(`SV`) |
|
||||||
|
| 신청완료 | `handleApplyClick` → 제출 | 체크리스트+필수첨부 검증 → `saveApply` + `LE` (상태코드 TBD) |
|
||||||
|
| 삭제 | `handleDelClick` | `useDeleteSewerBscPlan`(또는 동등) → 목록 |
|
||||||
|
| 보완완료 | `onSplmntCmptn` | `useSubmitSupplementSewerBscPlan` |
|
||||||
|
|
||||||
|
| JSP | 진행값 | URL | React |
|
||||||
|
| ---- | ---------- | ------------------------- | ---------------------------------------------------------- |
|
||||||
|
| 저장 | `SAVE` | `insertFnrscDmbp.do` | `saveApply` + 임시저장 코드 |
|
||||||
|
| 제출 | `PRESENTN` | `insertFnrscDscss.do` (!) | `saveApply` + 제출 상태코드 — **백엔드 상태코드 확인 TBD** |
|
||||||
|
|
||||||
|
페이로드 조립 (`buildApplyPayload`):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
SewerBscPlanApplySaveRequestDto {
|
||||||
|
sewerBscPlan, sewerBscPlanDtl,
|
||||||
|
sewerBscPlanOtlnList, bizCstList, fnrsCstList,
|
||||||
|
sewerBscPlanTmplntList, tmplntTmprStrgList?,
|
||||||
|
cklstResultList, esntlAtchFileList,
|
||||||
|
splmntDmndList?, rvwOpnnList? // 등록 화면에서는 보통 빈 배열
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
성공 후: `invalidateQueries(sewerBscPlanBaseKey)` → `router.push('/sewer-bsc-plan')` (신청완료는 상세로 보낼 수 있음 — fnrs와 동일 정책).
|
||||||
|
|
||||||
|
권한: JSP `AUTH3` → 기존 AS 권한 훅/역할로 버튼 `disabled`/`hidden`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. API / DTO 연결
|
||||||
|
|
||||||
|
Base: `/as-service/api/v1/sewer-bsc-plans`
|
||||||
|
엔티티: `@/entities/sewer-bsc-plan`
|
||||||
|
|
||||||
|
| 화면 동작 | Hook / Service | 비고 |
|
||||||
|
| -------------- | ---------------------------------------- | ---------------- |
|
||||||
|
| 등록 저장·제출 | `useSaveApplySewerBscPlan` → `saveApply` | 본 문서 핵심 |
|
||||||
|
| 수정 로드 | `useSewerBscPlanDetails(docNo, chgCycl)` | 편집 시 |
|
||||||
|
| 마스터만 | `useSewerBscPlan` | 목록 행 클릭 등 |
|
||||||
|
| 보완 제출 | `useSubmitSupplementSewerBscPlan` | edit + `prgrsSttsComCd=SL` 시 폼 하단 [보완완료] |
|
||||||
|
| 기술검토 | `useTechReviewSewerBscPlan` | 등록 JSP 범위 밖 |
|
||||||
|
|
||||||
|
등록 JSP에 없는 섹션(보완요구·검토의견)은 **상세/검토 화면**에서 `SplmntDmndFormSection` / 검토의견 섹션으로 후속 구현.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 권장 파일 배치
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/as/
|
||||||
|
├── app/(with-base-layout)/sewer-bsc-plan/
|
||||||
|
│ └── regist/page.tsx
|
||||||
|
├── src/pages/sewer-bsc-plan/
|
||||||
|
│ ├── lib/
|
||||||
|
│ │ ├── sewer-bsc-plan-regist-schema.ts # zod
|
||||||
|
│ │ └── form-data-mapper.ts # Det ↔ Form, Apply payload
|
||||||
|
│ ├── model/
|
||||||
|
│ │ └── (필요 시 table columns)
|
||||||
|
│ └── ui/
|
||||||
|
│ ├── sewer-bsc-plan-regist-page.tsx
|
||||||
|
│ ├── sewer-bsc-plan-form-page.tsx
|
||||||
|
│ ├── components/
|
||||||
|
│ │ └── previous-plan-lookup-dialog.tsx
|
||||||
|
│ └── sections/
|
||||||
|
│ ├── basic-info-form-section.tsx
|
||||||
|
│ ├── outline-form-section.tsx # 사업개요
|
||||||
|
│ ├── biz-cst-matrix-form-section.tsx # 총사업비 (전용)
|
||||||
|
│ ├── fnrs-cst-form-section.tsx # 재원분담
|
||||||
|
│ └── tmplnt-form-section.tsx # 처리시설 (Checkbox + Tabs)
|
||||||
|
└── docs/sewer-bsc-plan/
|
||||||
|
└── dmbp-regist-migration.md # 본 문서
|
||||||
|
```
|
||||||
|
|
||||||
|
공통 재사용 (신규 작성 최소화):
|
||||||
|
|
||||||
|
- `ContactInfoFormSection`, `PrivacyConsentSection`
|
||||||
|
- `ChecklistFormSection`, `AttachmentsFormSection`
|
||||||
|
- `SectionCollapsible`
|
||||||
|
- `Tabs` / `TabsList` / `TabsTrigger` / `TabsContent` (`@common/components` — 처리시설 패널)
|
||||||
|
|
||||||
|
도메인 전용 (재사용 금지):
|
||||||
|
|
||||||
|
- `BizCstMatrixFormSection` — 총사업비 (fnrs `BusinessCostFormSection` 미사용)
|
||||||
|
- 폼 하단 액션바 — `fnrs-form-page` 279~323 복제, API만 sewer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 화면 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입 (등록)
|
||||||
|
CALL 로그인사용자조회
|
||||||
|
CALL 코드조회(SEWERBSCPLANSE, SEWER, …) // 병렬
|
||||||
|
CALL 체크리스트트리(docSeComCd) // 병렬
|
||||||
|
CALL 필수첨부트리(docSeComCd) // 병렬
|
||||||
|
CALL 처리시설마스터목록 // TBD (BscFctList)
|
||||||
|
DISPLAY 폼 defaultValues
|
||||||
|
|
||||||
|
ON 1단계목표연도 CHANGE DO
|
||||||
|
SET 2·3·4단계 연도 (+5/+10/+15)
|
||||||
|
DISPLAY E/F/G/H 헤더 연도
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 처리시설 체크 DO
|
||||||
|
ADD tmplntList 항목
|
||||||
|
ADD TabsTrigger / TabsContent (시설 패널)
|
||||||
|
SET Tabs value = 신규 시설
|
||||||
|
CALL 이전기본계획수치(시설코드) // 당초열, TBD
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 처리시설 체크해제 DO
|
||||||
|
CONFIRM 제거
|
||||||
|
REMOVE tmplntList + 해당 Tabs 패널
|
||||||
|
SET Tabs value = 남은 시설 (없으면 Tabs 숨김)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON [이전기본계획조회] CLICK DO
|
||||||
|
OPEN PreviousPlanLookupDialog
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON [저장] CLICK DO
|
||||||
|
VALIDATE zod (동의 제외 가능)
|
||||||
|
CALL saveApply(payload, 임시저장 상태)
|
||||||
|
NAVIGATE 목록
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON [신청완료] CLICK DO
|
||||||
|
VALIDATE zod + privacyAgree=Y + 체크리스트 완료 + 필수첨부(esntlAtchYn=Y) 완료
|
||||||
|
CONFIRM 신청 확인
|
||||||
|
CALL saveApply(payload, 제출 상태 LE)
|
||||||
|
NAVIGATE 상세
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. JSP ↔ 신규 갭·리스크
|
||||||
|
|
||||||
|
| # | 이슈 | 대응 |
|
||||||
|
| --- | ---------------------------------------------------- | ------------------------------------------------- |
|
||||||
|
| 1 | JSP 제출 URL이 `dscss`로 분기 (오타/구프로세스 가능) | 신규는 `saveApply` 단일 진입으로 통일 |
|
||||||
|
| 2 | 총사업비 UI 계층 ≠ `BizCstItem` | `BizCstMatrixFormSection` 전용 + `acntgSeCd` 매핑 테이블 |
|
||||||
|
| 3 | 재원분담 ≠ fnrs shar | 전용 `fnrs-cst-form-section` |
|
||||||
|
| 4 | 처리시설 매트릭스(당초/현재/변경) | Checkbox 선택 → Tabs 패널; 저장은 변경 dtl 중심; 당초는 조회 전용 |
|
||||||
|
| 5 | 시설·이전계획 API | TBD — 미정 시 체크박스 목록 mock(`faker`)만 |
|
||||||
|
| 6 | `docSeComCd` 값 | CMS/체크리스트 마스터와 합의 |
|
||||||
|
| 7 | 레거시 multipart vs TUS | fnrs 첨부 패턴 준수 |
|
||||||
|
| 8 | 주석 처리된 상세 `formFields` (자연발생인구 등) | **축소된 formFields만** 이관; 주석 블록 복원 금지 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 구현 순서 (권장)
|
||||||
|
|
||||||
|
1. 라우트 + `FormPage` 골격 + zod 스키마(마스터·dtl·동의) + 폼 하단 액션바(§4-J)
|
||||||
|
2. B·C·D 섹션 (공통 위젯 최대 활용)
|
||||||
|
3. E 사업개요 + G 재원분담 (4열 매트릭스 패턴 공유)
|
||||||
|
4. F 총사업비 (`BizCstMatrixFormSection` 전용)
|
||||||
|
5. I 체크리스트·첨부
|
||||||
|
6. H 처리시설 (`BscFctList` Checkbox + `@common/components` Tabs — 가장 복잡)
|
||||||
|
7. 저장/신청완료/삭제/보완완료 API 연동 (§4-J)
|
||||||
|
8. 이전계획 Dialog (API 확정 후)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 체크리스트 (jsp-migration-as)
|
||||||
|
|
||||||
|
- [ ] contents만 이관 (include 제외)
|
||||||
|
- [ ] vercel-react-best-practices 적용 (Query 병렬·파생상태·dynamic)
|
||||||
|
- [ ] `@common/components` + 필요 시 DataTable
|
||||||
|
- [ ] API·모델은 `@/entities/sewer-bsc-plan` 우선
|
||||||
|
- [ ] 목데이터는 `@faker-js/faker`만 (시설 마스터 등 TBD 구간)
|
||||||
|
- [ ] 로딩·빈·에러 상태
|
||||||
|
- [ ] `router.push`로 목록 복귀
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 후속 문서
|
||||||
|
|
||||||
|
- 목록(`dmbpList.jsp`) / 상세(`dmbpView.jsp`) / 수정(`dmbpModify.jsp`)은 본 문서와 동일 섹션 체계로 별도 확장
|
||||||
|
- 화면설계서 SPEC 형식이 필요하면 `docs/sewer-bsc-plan/SPEC.md`로 분리 (영역 A~J 표 + API 표)
|
||||||
@@ -0,0 +1,498 @@
|
|||||||
|
# 화면설계서: 전국 행정지원 지도 (대시보드 메인)
|
||||||
|
|
||||||
|
## 1. 기본 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ---------- | -------------------------------------------- |
|
||||||
|
| 버전 | 1.2 |
|
||||||
|
| 프로세스명 | UI설계 |
|
||||||
|
| 화면명 | 전국 행정지원 지도 (대시보드 메인) |
|
||||||
|
| 화면 ID | TBD |
|
||||||
|
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
|
||||||
|
| 메뉴 경로 | 메인 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 화면 목적
|
||||||
|
|
||||||
|
로그인 사용자의 **기관 권한·당해 연도** 기준으로 문서별 진행상태 통계와 행정지원 알림을 한 화면에서 조회한다. 좌측 지도에서 전국 행정지원 현황을 보고, 우측에서 진행상황 카드·알림이력·공지사항을 확인한 뒤 알림 [처리하기]로 연계 업무 화면으로 이동한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 구성
|
||||||
|
|
||||||
|
### 3-1. 전체 레이아웃
|
||||||
|
|
||||||
|
브레드크럼·페이지 타이틀은 스크린샷에 없음. 홈(`/`) 메인 콘텐츠로 구성한다.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────┬─────────────────────────────────────────────────┐
|
||||||
|
│ [영역 A] 전국 행정지원 지도 │ [영역 B] 문서별 진행상황 카드 (2×2) │
|
||||||
|
│ 타이틀: 전국 행정지원 지도 │ ┌────────────────┐ ┌────────────────┐ │
|
||||||
|
│ │ │ 재원협의 │ │ 설치인가 │ │
|
||||||
|
│ (대한민국 시도·시군구 경계) │ │ 신청/진행/완료 │ │ 신청/진행/완료 │ │
|
||||||
|
│ 마커: 서울 강남구, │ ├────────────────┤ ├────────────────┤ │
|
||||||
|
│ 대전 유성구, │ │ 설계자문 │ │ 기본계획 │ │
|
||||||
|
│ 부산 해운대구 │ │ 신청/진행/완료 │ │ 신청/진행/완료 │ │
|
||||||
|
│ │ └────────────────┘ └────────────────┘ │
|
||||||
|
│ ├─────────────────────────────────────────────────┤
|
||||||
|
│ │ [영역 C] 알림이력 │
|
||||||
|
│ │ 알림이력 업데이트 HH:mm [전체보기] │
|
||||||
|
│ │ [오늘 N건] [지연 N건] [완료대기 N건] │
|
||||||
|
│ │ [뱃지] [일자] 제목 [처리하기] │
|
||||||
|
│ │ … (최대 5건) │
|
||||||
|
│ ├─────────────────────────────────────────────────┤
|
||||||
|
│ │ [영역 D] 공지사항 [더보기] │
|
||||||
|
│ │ ┌────┬────────┬────────┬────────┬──────┐ │
|
||||||
|
│ │ │번호│ 제목 │ 작성자 │ 작성일 │조회수│ │
|
||||||
|
│ │ └────┴────────┴────────┴────────┴──────┘ │
|
||||||
|
└────────────────────────────────┴─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3-2. 화면 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입
|
||||||
|
// searchYear 미전달 → 서버가 당해 연도로 고정. 알림 목록은 최신 5건
|
||||||
|
CALL getMainDashboard(DashboardMainSearchDto) // Authorization은 client 인터셉터
|
||||||
|
DISPLAY 진행상황 카드 (영역 B) // docPrgrsSttsList, TOTAL 행 제외
|
||||||
|
DISPLAY 알림 통계 칩 · 알림 목록 (영역 C) // notiStats, notiList
|
||||||
|
|
||||||
|
CALL getUserPostList(UserPostSearchParams) // board-service, AS_DASHBOARD_BBS_ID
|
||||||
|
DISPLAY 공지사항 그리드 (영역 D)
|
||||||
|
|
||||||
|
CALL 지도 마커/음영 조회(…) // TBD — 대시보드 API에 없음
|
||||||
|
DISPLAY 전국 지도 (영역 A)
|
||||||
|
|
||||||
|
ON 영역 C [전체보기] 버튼 CLICK DO
|
||||||
|
NAVIGATE /nti-hstry
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 C [처리하기] 버튼 CLICK DO
|
||||||
|
CALL readNoti(notiId) // entities/noti
|
||||||
|
NAVIGATE notiUrl // 비어 있으면 이동하지 않음
|
||||||
|
CALL getMainDashboard(…) // → 영역 C 갱신 (invalidate)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 D [더보기] 버튼 CLICK DO
|
||||||
|
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID} // as 앱 공지 목록 라우트 미구현. st/cm 관례
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 D 행 CLICK DO
|
||||||
|
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 B 카드 CLICK DO
|
||||||
|
NAVIGATE 해당 문서 목록 // §5-3
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 A 마커 CLICK DO
|
||||||
|
// TBD — 팝업/필터/이동 여부 미확정
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 영역 A — 전국 행정지원 지도
|
||||||
|
|
||||||
|
스크린샷 타이틀: **전국 행정지원 지도**.
|
||||||
|
|
||||||
|
`getMainDashboard` 응답에 지도·좌표·행정구역 음영 데이터는 **없다**. 마커·음영 소스 API는 **TBD**.
|
||||||
|
|
||||||
|
### 4-1. 표시 요소
|
||||||
|
|
||||||
|
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||||
|
| ----------- | --------- | ---- | ------ | ----------------- | ----------------------------------------------------------------- |
|
||||||
|
| 섹션 타이틀 | Text | — | — | — | 고정 문구 `전국 행정지원 지도` |
|
||||||
|
| 전국 지도 | Map | — | — | TBD | 시도·시군구 경계. 북·동부 음영(청) / 남서부 비음영(백) — 의미 TBD |
|
||||||
|
| 마커 라벨 | Marker | — | — | TBD | 스크린샷 샘플: `서울 강남구`, `대전 유성구`, `부산 해운대구` |
|
||||||
|
|
||||||
|
### 4-2. 상호작용
|
||||||
|
|
||||||
|
| 동작 | 설명 |
|
||||||
|
| ---------- | ----------------------------------------- |
|
||||||
|
| 마커 클릭 | TBD (상세 팝업 / 해당 지자체 필터 / 이동) |
|
||||||
|
| 지도 줌·팬 | TBD (스크린샷에 컨트롤 없음) |
|
||||||
|
|
||||||
|
`entities/adstdstr`는 시도·구군 **콤보**만 제공하며 좌표를 주지 않는다. 지도 라이브러리도 as 앱에 없음 — 선정 TBD.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 영역 B — 문서별 진행상황 카드
|
||||||
|
|
||||||
|
`DashboardMainResponseDto.docPrgrsSttsList`를 **`docSeComCd`로 매핑**해 4장 카드를 그린다. 배열 순서에 의존하지 않는다.
|
||||||
|
|
||||||
|
스크린샷 배치(좌→우, 상→하):
|
||||||
|
|
||||||
|
| 화면 카드 타이틀 | `docSeComCd` | API `docSeNm` |
|
||||||
|
| ----------------- | ------------ | -------------- |
|
||||||
|
| 재원협의 진행상황 | `FNRS` | 재원협의 |
|
||||||
|
| 설치인가 진행상황 | `INSTL` | 설치인가 |
|
||||||
|
| 설계자문 진행상황 | `CNFM` | 설계자문 |
|
||||||
|
| 기본계획 진행상황 | `SEWER` | 하수도기본계획 |
|
||||||
|
|
||||||
|
서버 SQL `ORDER BY`는 `INSTL → FNRS → SEWER → CNFM` 이며, `GROUP BY ROLLUP`으로 **`docSeComCd = TOTAL`** 합계 행이 추가된다. 카드에는 TOTAL 행을 **표시하지 않는다**.
|
||||||
|
|
||||||
|
카드 타이틀은 스크린샷 문구(`… 진행상황`)를 쓴다. `SEWER`의 API명 `하수도기본계획`은 화면에서 `기본계획`으로 축약한다.
|
||||||
|
|
||||||
|
### 5-1. 카드 내부 필드
|
||||||
|
|
||||||
|
각 카드 공통 구조. `totCnt`는 화면에 없음.
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------ | ---------- | ------------------ |
|
||||||
|
| 신청 | `reqCnt` | 파란 아이콘 + 건수 |
|
||||||
|
| 진행 | `prgrsCnt` | 초록 아이콘 + 건수 |
|
||||||
|
| 완료 | `cmptnCnt` | 주황 아이콘 + 건수 |
|
||||||
|
|
||||||
|
상태 그룹(서버): 임시저장(`SV`/`CV`) 제외. 문서별 `prgrs_stts_com_cd` → `REQ`/`PRG`/`CMP`.
|
||||||
|
|
||||||
|
### 5-2. 검색 파라미터 (화면 미표시)
|
||||||
|
|
||||||
|
영역 B에 연도·필터 UI는 없다. 서버 기본값을 따른다.
|
||||||
|
|
||||||
|
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||||
|
| ------ | --------- | ---- | --------- | ----------------- | --------------------------------------------------------------------- |
|
||||||
|
| (없음) | — | — | 당해 연도 | `searchYear` | 미전달 시 서버 `LocalDate.now().getYear()`. 연도 셀렉터 추가 여부 TBD |
|
||||||
|
|
||||||
|
`docSeComCd` 쿼리는 **알림 목록 필터**에만 쓰이며, 진행상황 SQL에는 전달되지 않는다. 카드 4종은 항상 전원 집계한다.
|
||||||
|
|
||||||
|
### 5-3. 상호작용
|
||||||
|
|
||||||
|
카드 클릭 시 **해당 문서 목록 페이지**로 이동한다. `docSeComCd` → 라우트 고정 매핑.
|
||||||
|
|
||||||
|
| 동작 | `docSeComCd` | 이동 경로 |
|
||||||
|
| --------- | ------------ | ----------------- |
|
||||||
|
| 카드 클릭 | `FNRS` | `/fnrs-cnsltn` |
|
||||||
|
| 카드 클릭 | `INSTL` | `/instl-sncn` |
|
||||||
|
| 카드 클릭 | `CNFM` | `/design-cnstn` |
|
||||||
|
| 카드 클릭 | `SEWER` | `/sewer-bsc-plan` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 영역 C — 알림이력
|
||||||
|
|
||||||
|
서버가 cms-service 알림을 대시보드 응답에 합친다. 목록은 **page=0, size=5** (최신 5건). 페이지네이션 없음.
|
||||||
|
|
||||||
|
### 6-1. 헤더
|
||||||
|
|
||||||
|
| 표시명 | Field / 소스 | 비고 |
|
||||||
|
| ------------- | ------------ | --------------------------------------------------------------------------------------- |
|
||||||
|
| 섹션 타이틀 | — | 고정 문구 `알림이력` |
|
||||||
|
| 업데이트 시각 | (client) | 스크린샷 `업데이트 10:32`. API 필드 없음. `getMainDashboard` 마지막 fetch 시각(`HH:mm`) |
|
||||||
|
| 전체보기 | Button | `/nti-hstry`로 이동 |
|
||||||
|
|
||||||
|
### 6-2. 통계 칩
|
||||||
|
|
||||||
|
`NotiAsStatResponseDto`.
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------------ | -------------- | -------------------------------------- |
|
||||||
|
| 오늘 N건 | `todayCount` | 당일 00:00 이후 수신 |
|
||||||
|
| 지연 N건 | `delayCount` | `notReadYn=N` 이고 수신 후 3일 이상 |
|
||||||
|
| 완료대기 N건 | `pendingCount` | `notReadYn=Y` 이고 `notiPrcsCmptnYn=N` |
|
||||||
|
|
||||||
|
### 6-3. 목록 행
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------ | --------- | --------------------------------------------- |
|
||||||
|
| 일자 | `regDt` | 표시 `YYYY-MM-DD`. 응답 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
| 제목 | `notiTtl` | 알림 제목 |
|
||||||
|
| 처리 | (action) | 스크린샷 라벨 `처리하기`. 클릭 §6-4 |
|
||||||
|
|
||||||
|
응답에 있으나 이 목록에 **표시하지 않음**:
|
||||||
|
|
||||||
|
| Field | 비고 |
|
||||||
|
| ----------------- | ----------------------------------------- |
|
||||||
|
| `notiId` | 행 키 · `readNoti` 파라미터 |
|
||||||
|
| `recvUnqId` | 수신 회원 UUID (서버 로그인 사용자 고정) |
|
||||||
|
| `senderUnqId` | 발신 회원 UUID |
|
||||||
|
| `notiType` | `ADSS` |
|
||||||
|
| `notiCn` | 본문 |
|
||||||
|
| `notiUrl` | 처리 후 이동 URL. 컬럼 미표시 |
|
||||||
|
| `notiLnkgUnqNo` | 알림연결고유번호 |
|
||||||
|
| `docSeComCd` | 문서구분 코드 |
|
||||||
|
| `docSeComNm` | 문서구분명 (알림이력 화면에서는 연계업무) |
|
||||||
|
| `notReadYn` | 뱃지 판정 TBD에 사용 가능 |
|
||||||
|
| `notiIdntyYn` | 확인 여부 |
|
||||||
|
| `notiIdntyDt` | 확인 일시 |
|
||||||
|
| `notiPrcsCmptnYn` | 처리 완료 여부 |
|
||||||
|
| `notiPrcsCmptnDt` | 처리 완료 일시 |
|
||||||
|
|
||||||
|
### 6-4. 행 상호작용
|
||||||
|
|
||||||
|
| 동작 | 설명 |
|
||||||
|
| --------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| [전체보기] 클릭 | `/nti-hstry` (알림이력 목록)로 이동 |
|
||||||
|
| [처리하기] 클릭 | `readNoti(notiId)` 후 `notiUrl`로 이동. URL이 비면 이동하지 않음(읽음 처리는 수행). `dashboardBaseKey` invalidate |
|
||||||
|
|
||||||
|
`DashboardController`에 읽음 API는 없다. **`entities/noti`의 `readNoti`** 를 쓴다. 알림이력 화면과 동일 (`PUT /cms-service/api/v1/noti/{notiId}/read`).
|
||||||
|
|
||||||
|
### 6-5. 검색 파라미터 (화면 미표시)
|
||||||
|
|
||||||
|
서버 `toNotiSearchDto()` 기본: `notiType = ADSS`. 화면 필터 없음.
|
||||||
|
|
||||||
|
| DashboardMainSearchDto | 화면 | 비고 |
|
||||||
|
| ---------------------- | ---- | --------------------------------- |
|
||||||
|
| `notiType` | 없음 | 서버 기본 `ADSS` |
|
||||||
|
| `notReadYn` | 없음 | 통계 칩 클릭 필터 시 후보 (TBD) |
|
||||||
|
| `startDt` / `endDt` | 없음 | 대시보드 기본 미전달 |
|
||||||
|
| `docSeComCd` | 없음 | 알림만 필터. 진행상황 카드와 무관 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 영역 D — 공지사항
|
||||||
|
|
||||||
|
`getMainDashboard`에 공지 필드는 없다. **board-service** `getUserPostList`로 별도 조회한다.
|
||||||
|
|
||||||
|
- Base: `GET /board-service/api/v1/posts/user/{AS_DASHBOARD_BBS_ID}`
|
||||||
|
- `AS_DASHBOARD_BBS_ID` = `NEXT_PUBLIC_AS_DASHBOARD_BBS_ID` (로컬 예: `7`)
|
||||||
|
- 클라이언트: `@/entities/board` — `postApi.getUserPostList` / `useUserPostList`
|
||||||
|
|
||||||
|
페이지네이션 컨트롤은 없다. 요약 목록만 표시한다 (`page=0`, `size=3`).
|
||||||
|
|
||||||
|
### 7-1. 헤더·버튼
|
||||||
|
|
||||||
|
| 버튼명 | 동작 |
|
||||||
|
| ------ | ------------------------------------------------------------------------------------------- |
|
||||||
|
| 더보기 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}` 로 이동. as 앱에 공지 목록 라우트는 없음(st/cm 관례) |
|
||||||
|
|
||||||
|
### 7-2. 그리드 컬럼
|
||||||
|
|
||||||
|
스크린샷 헤더 순서. `UserPost`.
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------ | --------- | ---------------------------------------------------- |
|
||||||
|
| 번호 | `rowNo` | `upndFixYn` = `Y` 이면 `[공지]` Badge, 그 외 `rowNo` |
|
||||||
|
| 제목 | `pstTtl` | truncate |
|
||||||
|
| 작성자 | `wrtrNm` | |
|
||||||
|
| 작성일 | `regDt` | 표시 `YYYY-MM-DD` (응답 datetime) |
|
||||||
|
| 조회수 | `inqNocs` | 숫자 |
|
||||||
|
|
||||||
|
조회 파라미터 (`UserPostSearchParams`):
|
||||||
|
|
||||||
|
| 필드 | 값 | 비고 |
|
||||||
|
| ------------- | ----------- | ------------------------------------------ |
|
||||||
|
| `page` | `0` | Spring 0-based |
|
||||||
|
| `size` | `3` | 대시보드 요약. 스크린샷 3행 |
|
||||||
|
| `keywordType` | `bbsTypeCd` | st 대시보드 공지 패널과 동일 |
|
||||||
|
| `keyword` | `BBSNOTICE` | 게시판 유형. path의 bbsId와 함께 공지 한정 |
|
||||||
|
|
||||||
|
응답에 있으나 그리드에 **표시하지 않음**: `bbsId`, `pstNo`(행 키·상세 이동), `pstCn`, `upndFixYn`(번호 열 판정만), `rgtrNm`, `atflCd` 등.
|
||||||
|
|
||||||
|
### 7-3. 행 상호작용
|
||||||
|
|
||||||
|
| 동작 | 설명 |
|
||||||
|
| ------- | ------------------------------------------------------------------------------ |
|
||||||
|
| 행 클릭 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}` (as 앱 공지 상세 라우트 미구현) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. API/DTO 명세
|
||||||
|
|
||||||
|
### 8-1. 공통
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
| --------------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| Base prefix | `/as-service/api/v1/dashboard` (`ENDPOINT.AS_SERVICE.DASHBOARD`) |
|
||||||
|
| 인증 | `Authorization` 헤더. 프론트는 `client` 인터셉터. 로그인 기관 권한은 서버가 사용자 캐시로 적용 |
|
||||||
|
| 클라이언트 모듈 | `@/entities/dashboard` — `dashboardService` / `useGetMainDashboard` |
|
||||||
|
| 알림 읽음 | `@/entities/noti` — `notiService.readNoti` / `useReadNoti` |
|
||||||
|
| 공지사항 | `@/entities/board` — `postApi.getUserPostList` / `useUserPostList` |
|
||||||
|
|
||||||
|
영역 A(지도)는 본 컨트롤러·board-service 범위 밖 → **TBD**.
|
||||||
|
|
||||||
|
### 8-2. 대시보드 메인 조회 — `getMainDashboard`
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| -------------------------------------------- | ------ | ------------- | ------------------------------- | -------------------------- | ----------------------------------------------------------- |
|
||||||
|
| 대시보드 메인 통합진행상태 및 알림 정보 조회 | GET | `{BASE}/main` | Query: `DashboardMainSearchDto` | `DashboardMainResponseDto` | `dashboardService.getMainDashboard` / `useGetMainDashboard` |
|
||||||
|
|
||||||
|
**Request (`DashboardMainSearchDto`)**
|
||||||
|
|
||||||
|
| 필드 | 필수 | 설명 |
|
||||||
|
| ------------ | ---- | ---------------------------------------------------------------------- |
|
||||||
|
| `searchYear` | N | 검색 연도. 미전달 시 서버 당해 연도. 영역 B 집계 기간 |
|
||||||
|
| `notiType` | N | 알림 종류. 미전달 시 서버 `ADSS` |
|
||||||
|
| `notReadYn` | N | 알림 확인 여부 `Y`/`N`. 본 화면 미사용 |
|
||||||
|
| `startDt` | N | 알림 시작일 `YYYY-MM-DD`. 본 화면 미사용 |
|
||||||
|
| `endDt` | N | 알림 종료일 `YYYY-MM-DD`. 본 화면 미사용 |
|
||||||
|
| `docSeComCd` | N | 알림 문서구분 (`INSTL`/`FNRS`/`SEWER`/`CNFM` 등). 진행상황 집계와 무관 |
|
||||||
|
|
||||||
|
`Authorization`은 service 시그니처에 넣지 않는다.
|
||||||
|
|
||||||
|
**Response (`DashboardMainResponseDto`)**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
| ------------------ | --------------------------- | --------------------------- |
|
||||||
|
| `docPrgrsSttsList` | `DocPrgrsSttsResponseDto[]` | 문서별 진행상태 + TOTAL 행 |
|
||||||
|
| `notiList` | `NotiAsUserResponseDto[]` | 최신 최대 5건 |
|
||||||
|
| `notiStats` | `NotiAsStatResponseDto` | 오늘 / 지연 / 완료대기 건수 |
|
||||||
|
|
||||||
|
**`DocPrgrsSttsResponseDto`**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
| ------------ | ------ | --------------------------------------------- |
|
||||||
|
| `docSeNm` | string | 문서 구분 명칭 |
|
||||||
|
| `docSeComCd` | string | `INSTL` / `FNRS` / `SEWER` / `CNFM` / `TOTAL` |
|
||||||
|
| `reqCnt` | number | 신청 건수 |
|
||||||
|
| `prgrsCnt` | number | 진행 건수 |
|
||||||
|
| `cmptnCnt` | number | 완료 건수 |
|
||||||
|
| `totCnt` | number | 총 건수 (화면 미표시) |
|
||||||
|
|
||||||
|
**`NotiAsStatResponseDto`**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
| -------------- | ------ | --------- |
|
||||||
|
| `todayCount` | number | 오늘 알림 |
|
||||||
|
| `delayCount` | number | 지연 알림 |
|
||||||
|
| `pendingCount` | number | 완료대기 |
|
||||||
|
|
||||||
|
**`NotiAsUserResponseDto`** — 필드 상세는 `docs/spec/nti-hstry` §6-2와 동일 (`notiId`, `notiTtl`, `regDt`, `notiUrl`, `notReadYn`, `notiPrcsCmptnYn` 등).
|
||||||
|
|
||||||
|
### 8-3. 알림 읽음 처리 — `readNoti`
|
||||||
|
|
||||||
|
대시보드 컨트롤러가 아니라 cms-service. 영역 C [처리하기]에서 호출.
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| -------------- | ------ | ---------------------------------------- | ----------------------- | ---------------- | -------------------------------------- |
|
||||||
|
| 알림 읽음 처리 | PUT | `/cms-service/api/v1/noti/{notiId}/read` | Path: `notiId` (`Long`) | `Long` (알림 ID) | `notiService.readNoti` / `useReadNoti` |
|
||||||
|
|
||||||
|
Body 없음. 성공 시 `dashboardBaseKey`(및 필요 시 `notiBaseKey`) invalidate.
|
||||||
|
|
||||||
|
### 8-4. 전국 행정지원 지도 — TBD
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| ------------------- | ------ | --- | ----------- | ------------ | --- |
|
||||||
|
| 지도 마커·음영 조회 | TBD | TBD | TBD | TBD | TBD |
|
||||||
|
|
||||||
|
### 8-5. 공지사항 — `getUserPostList`
|
||||||
|
|
||||||
|
board-service. bbsId는 환경변수 `AS_DASHBOARD_BBS_ID`로 path에 고정한다.
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| ----------------------- | ------ | ------------------------------------------ | ----------------------------- | ------------------------ | --------------------------------------------- |
|
||||||
|
| 대시보드 공지 요약 목록 | GET | `/board-service/api/v1/posts/user/{bbsId}` | Query: `UserPostSearchParams` | `PageResponse<UserPost>` | `postApi.getUserPostList` / `useUserPostList` |
|
||||||
|
|
||||||
|
**Request (`UserPostSearchParams`)** — 본 화면 고정값
|
||||||
|
|
||||||
|
| 필드 | 필수 | 설명 |
|
||||||
|
| ------------- | ---- | -------------- |
|
||||||
|
| `page` | N | `0` |
|
||||||
|
| `size` | N | `3` |
|
||||||
|
| `keywordType` | N | `bbsTypeCd` |
|
||||||
|
| `keyword` | N | `BBSNOTICE` |
|
||||||
|
| `popupYn` 등 | N | 본 화면 미사용 |
|
||||||
|
|
||||||
|
**Response (`UserPost`) — 화면 매핑 필드**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
| ----------- | ------ | --------------------------- |
|
||||||
|
| `rowNo` | number | 번호. 고정글이 아니면 표시 |
|
||||||
|
| `pstNo` | number | 게시 번호. 상세 이동 키 |
|
||||||
|
| `bbsId` | number | 게시판 ID |
|
||||||
|
| `pstTtl` | string | 제목 |
|
||||||
|
| `wrtrNm` | string | 작성자 |
|
||||||
|
| `regDt` | string | 작성일 |
|
||||||
|
| `inqNocs` | number | 조회수 |
|
||||||
|
| `upndFixYn` | string | 상단고정 `Y`/`N` → `[공지]` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. UI 구현 가이드
|
||||||
|
|
||||||
|
### 9-1. 기술 스택·원칙
|
||||||
|
|
||||||
|
- 라우트: 기존 홈 `app/(main)/(with-base-layout)/page.tsx` → `pages/home` (현재 `MainPage` 플레이스홀더)
|
||||||
|
- UI: `@common/components` — Card, Badge, Button, Table(공지). 지도 라이브러리 TBD
|
||||||
|
- 데이터: React Query `useGetMainDashboard()`. 처리하기는 `useReadNoti()`. 공지는 `useUserPostList()`
|
||||||
|
- 진행상황 카드: `docSeComCd`로 4슬롯 매핑. `TOTAL` 제외
|
||||||
|
- 알림 목록: 테이블이 아닌 행 리스트(뱃지 + 일자 + 제목 + 버튼)
|
||||||
|
- 공지: `@tanstack/react-table` + `useDataTable`, 페이지네이션 없음
|
||||||
|
- 검색 조건 URL 동기화: 본 화면 필터 없음. `searchYear` UI가 생기면 그때 검토
|
||||||
|
|
||||||
|
### 9-2. 모듈·파일 배치
|
||||||
|
|
||||||
|
| 구분 | 경로 |
|
||||||
|
| ------------- | ------------------------------------------------------------ |
|
||||||
|
| 라우트 | `app/(main)/(with-base-layout)/page.tsx` |
|
||||||
|
| 페이지 | `pages/dashboard/ui/dashboard-page.tsx` |
|
||||||
|
| 지도 | `pages/dashboard/ui/national-as-map.tsx` (API TBD, Skeleton) |
|
||||||
|
| 진행상황 카드 | `pages/dashboard/ui/doc-prgrs-stts-cards.tsx` |
|
||||||
|
| 알림이력 | `pages/dashboard/ui/dashboard-noti-list.tsx` |
|
||||||
|
| 공지사항 | `pages/dashboard/ui/dashboard-notice-table.tsx` |
|
||||||
|
| 공지 컬럼 | `pages/dashboard/model/dashboard-notice-table-columns.tsx` |
|
||||||
|
| Entity API | `entities/dashboard/api/dashboard-service.ts` |
|
||||||
|
| Entity Hook | `entities/dashboard/api/queries.ts` → `useGetMainDashboard` |
|
||||||
|
| Entity Model | `entities/dashboard/model/dashboard-interface.ts` |
|
||||||
|
| 알림 읽음 | `entities/noti` → `useReadNoti` |
|
||||||
|
| 공지 Entity | `entities/board` → `useUserPostList` |
|
||||||
|
|
||||||
|
Entity(`getMainDashboard`, `getUserPostList`) 구현됨. 영역 B·C·D 화면 구현. 영역 A는 Skeleton.
|
||||||
|
|
||||||
|
### 9-3. 영역별 UI 매핑
|
||||||
|
|
||||||
|
| 영역 | 컴포넌트 | 데이터 |
|
||||||
|
| ---- | ------------------------ | ---------------------------------------------------------------------------- |
|
||||||
|
| A | `national-as-map` | Skeleton (지도 API TBD) |
|
||||||
|
| B | `doc-prgrs-stts-cards` | `useGetMainDashboard` → `docPrgrsSttsList` (`TOTAL` 제외, 코드 매핑) |
|
||||||
|
| C | `dashboard-noti-list` | 동 훅 → `notiStats`, `notiList`. 처리 → `useReadNoti`. 전체보기 `/nti-hstry` |
|
||||||
|
| D | `dashboard-notice-table` | `useUserPostList` → `content[]`. [더보기]·행 클릭 → `/cms/bbsnotice/...` |
|
||||||
|
|
||||||
|
### 9-4. 다이얼로그·라우팅
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| -------------- | -------------------------------------------------------------------------------------- |
|
||||||
|
| 다이얼로그 | 없음 |
|
||||||
|
| 본 화면 라우트 | `/` (홈) |
|
||||||
|
| 알림 처리 이동 | [처리하기] → `readNoti` 후 `notiUrl` (알림이력과 동일, SEWER URL은 상세 세그먼트 제거) |
|
||||||
|
| 알림 전체보기 | `/nti-hstry` |
|
||||||
|
| 공지 더보기 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}` (as 앱 공지 라우트 미구현, st/cm 관례) |
|
||||||
|
| 공지 행 클릭 | `/cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}` |
|
||||||
|
| 진행상황 카드 | §5-3 목록 라우트 (`/fnrs-cnsltn`, `/instl-sncn`, `/design-cnstn`, `/sewer-bsc-plan`) |
|
||||||
|
|
||||||
|
### 9-5. 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입 (홈)
|
||||||
|
CALL getMainDashboard({})
|
||||||
|
DISPLAY 영역 B // docPrgrsSttsList — FNRS/INSTL/CNFM/SEWER 슬롯
|
||||||
|
DISPLAY 영역 C // notiStats + notiList(≤5)
|
||||||
|
|
||||||
|
CALL getUserPostList({ page: 0, size: 3, keywordType: "bbsTypeCd", keyword: "BBSNOTICE" })
|
||||||
|
DISPLAY 영역 D
|
||||||
|
|
||||||
|
CALL 지도 데이터 // TBD → 영역 A
|
||||||
|
|
||||||
|
ON 영역 C [전체보기] CLICK DO
|
||||||
|
NAVIGATE /nti-hstry
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 C [처리하기] CLICK DO
|
||||||
|
CALL readNoti(notiId)
|
||||||
|
IF notiUrl THEN
|
||||||
|
NAVIGATE notiUrl
|
||||||
|
END IF
|
||||||
|
CALL getMainDashboard // invalidate dashboardBaseKey
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 B 카드 CLICK DO
|
||||||
|
NAVIGATE 문서 목록 // §5-3
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 D [더보기] CLICK DO
|
||||||
|
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 D 행 CLICK DO
|
||||||
|
NAVIGATE /cms/bbsnotice/{AS_DASHBOARD_BBS_ID}/{pstNo}
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
# 화면설계서: 내 행정지원
|
||||||
|
|
||||||
|
## 1. 기본 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ---------- | -------------------------------------------- |
|
||||||
|
| 버전 | 1.0 |
|
||||||
|
| 프로세스명 | UI설계 |
|
||||||
|
| 화면명 | 내 행정지원 |
|
||||||
|
| 화면 ID | AS_SC0204011 |
|
||||||
|
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
|
||||||
|
| 메뉴 경로 | 메인 > 행정지원서비스 > 내 행정지원 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 화면 목적
|
||||||
|
|
||||||
|
로그인한 사용자가 **본인이 신청한 행정지원 문서**를 기간·구분·진행상태로 조회한다. 목록에서 행을 선택하면 해당 문서 상세 화면으로 이동한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 구성
|
||||||
|
|
||||||
|
### 3-1. 전체 레이아웃
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 브레드크럼: 메인 > 행정지원서비스 > 내 행정지원 │
|
||||||
|
│ 페이지 타이틀: 내 행정지원 │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 A] 검색 필터 │
|
||||||
|
│ 기간 [시작일] ~ [종료일] 구분 ▼ 진행상태 ▼ [검색] │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 B] 내 신청 목록 │
|
||||||
|
│ 타이틀: 내 신청 목록 N개 │
|
||||||
|
│ ┌────┬──────┬────────┬──────────────┬────────────┬────────┬──────────────┐ │
|
||||||
|
│ │ No │ 구분 │ 신청일 │ 진행상태 │ 완료(예정)일│ 담당자 │ 담당자연락처 │ │
|
||||||
|
│ ├────┼──────┼────────┼──────────────┼────────────┼────────┼──────────────┤ │
|
||||||
|
│ │ … │ … │ … │ prgrsSttsComNm│ … │ … │ … │ │
|
||||||
|
│ └────┴──────┴────────┴──────────────┴────────────┴────────┴──────────────┘ │
|
||||||
|
│ [페이지네이션] │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3-2. 화면 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입
|
||||||
|
// 기간 기본값: 오늘 기준 최근 1개월 (startDate = today-1M, endDate = today)
|
||||||
|
CALL findMyElaperDocList(ElaprMyDocSearchDto + pageable)
|
||||||
|
DISPLAY 내 신청 목록 그리드 (영역 B)
|
||||||
|
|
||||||
|
ON [검색] 버튼 CLICK DO
|
||||||
|
CALL findMyElaperDocList(영역 A 조건 + page=0)
|
||||||
|
DISPLAY 내 신청 목록 그리드 (영역 B)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 페이지네이션 CHANGE DO
|
||||||
|
CALL findMyElaperDocList(영역 A 조건 + page/size)
|
||||||
|
DISPLAY 내 신청 목록 그리드 (영역 B)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 B 행 CLICK DO
|
||||||
|
// docSeComCd·prgrsSttsComCd·rgtr → view/edit
|
||||||
|
NAVIGATE 문서 상세 또는 수정 화면
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 영역 A — 검색 필터
|
||||||
|
|
||||||
|
구성: **기간 · 구분 · 진행상태** (+ 검색).
|
||||||
|
|
||||||
|
> UI설계 상세설명의 `docSecs` / `docStatusCd`는 레거시·설계 문서 명칭이다.
|
||||||
|
> **API SoT**는 `ElaprMyDocSearchDto`의 `docSeComCd` / `prgrsSttsComCd`를 사용한다.
|
||||||
|
|
||||||
|
### 4-1. 필드 정의
|
||||||
|
|
||||||
|
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||||
|
| ---------- | ---------------- | ---- | --------------------------- | ----------------- | --------------------------------------------------------------------- |
|
||||||
|
| 기간(시작) | DatePicker | N | 오늘 − 1개월 (`YYYY-MM-DD`) | `startDate` | 신청일(`reqDt`) 기준 검색으로 가정 |
|
||||||
|
| 기간(종료) | DatePicker | N | 오늘 (`YYYY-MM-DD`) | `endDate` | |
|
||||||
|
| 구분 | Select(공통코드) | N | 전체(미선택) | `docSeComCd` | 상위코드 `ASDOCSE`. UI설계 `docSecs` → API `docSeComCd` |
|
||||||
|
| 진행상태 | Select(공통코드) | N | 전체(미선택) | `prgrsSttsComCd` | 상위코드 = `{docSeComCd}STTS` (예: `INSTL` → `INSTLSTTS`). UI설계 `docStatusCd` → API 필드명 |
|
||||||
|
|
||||||
|
### 4-2. 버튼
|
||||||
|
|
||||||
|
| 버튼명 | 동작 |
|
||||||
|
| ------ | ----------------------------------------------------------------- |
|
||||||
|
| 검색 | 영역 A 조건으로 `findMyElaperDocList` 호출 → 영역 B 갱신 (page=0) |
|
||||||
|
|
||||||
|
### 4-3. 콤보 의존
|
||||||
|
|
||||||
|
| 트리거 | 동작 |
|
||||||
|
| ----------- | ------------------------------------------------------------------- |
|
||||||
|
| 구분 변경 | 진행상태 옵션을 `upCd = docSeComCd + "STTS"`로 재조회. 진행상태 선택값 RESET |
|
||||||
|
| 구분 미선택 | 진행상태 비활성(옵션 비움). `upCd` 조합 불가이므로 조회하지 않음 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 영역 B — 내 신청 목록
|
||||||
|
|
||||||
|
### 5-1. 그리드 컬럼
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------------ | ---------------- | ------------------------------------------------- |
|
||||||
|
| No | (row index) | 페이지 오프셋 반영 |
|
||||||
|
| 구분 | `docSeComNm` | 코드 `docSeComCd` (`ASDOCSE`) |
|
||||||
|
| 신청일 | `reqDt` | 표시 `YYYY-MM-DD` (응답은 `yyyy-MM-dd HH:mm:ss`) |
|
||||||
|
| 진행상태 | `prgrsSttsComNm` | 텍스트 렌더링 (단계형 프로그레스 미사용) |
|
||||||
|
| 완료(예정)일 | `cmptnExpctDt` | 표시 `YYYY-MM-DD` |
|
||||||
|
| 담당자 | `picNm` | |
|
||||||
|
| 담당자연락처 | `picTelno` | |
|
||||||
|
|
||||||
|
응답에 있으나 그리드에 **표시하지 않음**:
|
||||||
|
|
||||||
|
| Field | 비고 |
|
||||||
|
| ---------------- | ----------------------------------------------------------------- |
|
||||||
|
| `docNo` | 문서번호 — 행 클릭 라우팅 키 |
|
||||||
|
| `docTtl` | 문서명/사업명 — 미표시 |
|
||||||
|
| `chgCycl` | 변경차수 — 그리드 미표시. SEWER 이동 시 query 파라미터로 사용 |
|
||||||
|
| `prgrsSttsComCd` | 진행상태 코드 — 컬럼 표시는 `prgrsSttsComNm` |
|
||||||
|
| `rgtr` | 등록자 UUID — edit 판정용 (`rgtr === userUnqId`) |
|
||||||
|
|
||||||
|
### 5-2. 목록 메타·페이지네이션
|
||||||
|
|
||||||
|
| 항목 | 매핑 |
|
||||||
|
| ------------- | ------------------------------------------- |
|
||||||
|
| 건수 `N개` | `PageResponse.totalElements` |
|
||||||
|
| page / size | `ElaprMyDocSearchDto`의 `PageableParams` |
|
||||||
|
| 페이지 컨트롤 | Spring `Page` 표준 (`number`, `totalPages`) |
|
||||||
|
|
||||||
|
### 5-3. 행 상호작용
|
||||||
|
|
||||||
|
| 동작 | 설명 |
|
||||||
|
| ------- | -------------------------------------------------------------------------------------------- |
|
||||||
|
| 행 클릭 | `docSeComCd`·`prgrsSttsComCd`·`rgtr`로 view/edit 분기 후 이동 (`resolveMyPbadmsSprtDetailPath`) |
|
||||||
|
|
||||||
|
**edit 조건** (기존 도메인 목록과 동일):
|
||||||
|
|
||||||
|
| `docSeComCd` | edit 조건 |
|
||||||
|
| ------------------------ | ---------------------------------------------------------------------- |
|
||||||
|
| `INSTL` / `FNRS` / `SEWER` | `(prgrsSttsComCd === "SV" \|\| "SL") && rgtr === loggedInUserUnqId` |
|
||||||
|
| `CNFM` | `prgrsSttsComCd === "CV" && rgtr === loggedInUserUnqId` |
|
||||||
|
|
||||||
|
| `docSeComCd` | edit 경로 | view 경로 |
|
||||||
|
| ------------ | -------------------------------------------- | --------------------------------------- |
|
||||||
|
| `FNRS` | `/fnrs-cnsltn/{docNo}/edit` | `/fnrs-cnsltn/{docNo}` |
|
||||||
|
| `INSTL` | `/instl-sncn/{docNo}/edit` | `/instl-sncn/{docNo}` |
|
||||||
|
| `CNFM` | `/design-cnstn/{docNo}/edit` | `/design-cnstn/{docNo}` |
|
||||||
|
| `SEWER` | `/sewer-bsc-plan/{docNo}/edit?chgCycl={n}` | `/sewer-bsc-plan/{docNo}?chgCycl={n}` |
|
||||||
|
| 그 외 | 이동하지 않음 | |
|
||||||
|
|
||||||
|
- SEWER `chgCycl`: `row.chgCycl ?? 1`
|
||||||
|
- 조건 불충족 시 항상 view
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. API/DTO 명세
|
||||||
|
|
||||||
|
### 6-1. 공통
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
| --------------- | ------------------------------------------------------------------------------------------ |
|
||||||
|
| Base prefix | `/as-elapr-service/api/v1/elapr-prgrs-stts` (`ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS`) |
|
||||||
|
| 인증 | 로그인 사용자 UUID — 서버 `SecurityUtils.getLoggedInUserUnqIdUUID()` (클라이언트 미전달) |
|
||||||
|
| 클라이언트 모듈 | `@/entities/elapr` — `elaprPrgrsSttsService` / `useElaprPrgrsSttsMyDocPage` |
|
||||||
|
|
||||||
|
### 6-2. 내 행정지원 목록 — `findMyElaperDocList`
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| ----------------- | ------ | --------------- | --------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------- |
|
||||||
|
| 내 신청 목록 조회 | GET | `{BASE}/my/doc` | Query: `ElaprMyDocSearchDto` + Pageable | `PageResponse<MyAdssDocResponseDto>` | `elaprPrgrsSttsService.findMyElaperDocList` / `useElaprPrgrsSttsMyDocPage` |
|
||||||
|
|
||||||
|
**Request (`ElaprMyDocSearchDto` + Pageable)**
|
||||||
|
|
||||||
|
| 필드 | 필수 | 설명 |
|
||||||
|
| ------------------------ | ---- | ------------------------------------------------------------ |
|
||||||
|
| `startDate` | N | 검색 시작일 `YYYY-MM-DD` |
|
||||||
|
| `endDate` | N | 검색 종료일 `YYYY-MM-DD` |
|
||||||
|
| `docSeComCd` | N | 문서구분 (`INSTL` / `FNRS` / `DESIGN` / `SEWER` / `PWRK` 등) |
|
||||||
|
| `prgrsSttsComCd` | N | 문서진행상태공통코드 |
|
||||||
|
| `page` / `size` / `sort` | N | Spring Pageable |
|
||||||
|
|
||||||
|
**Response (`MyAdssDocResponseDto`)**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
| ---------------- | ------- | ------------------------------------------- |
|
||||||
|
| `docNo` | string | 문서번호 |
|
||||||
|
| `docTtl` | string? | 문서명/사업명 |
|
||||||
|
| `docSeComCd` | string? | 문서구분 코드 |
|
||||||
|
| `docSeComNm` | string? | 문서구분 코드명 (`ASDOCSE`) |
|
||||||
|
| `reqDt` | string? | 신청일시 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
| `prgrsSttsComCd` | string? | 문서진행상태 코드 |
|
||||||
|
| `prgrsSttsComNm` | string? | 문서진행상태 코드명 |
|
||||||
|
| `cmptnExpctDt` | string? | 완료(예정)일 `yyyy-MM-dd` |
|
||||||
|
| `picNm` | string? | 담당자 성명 |
|
||||||
|
| `picTelno` | string? | 담당자 연락처 |
|
||||||
|
| `chgCycl` | number? | 변경차수 |
|
||||||
|
| `rgtr` | string? | 등록자 UUID |
|
||||||
|
|
||||||
|
### 6-3. 공통코드 (콤보)
|
||||||
|
|
||||||
|
| 용도 | 상위코드 | API | 비고 |
|
||||||
|
| -------- | ------------ | --------------------------- | ----------------------------- |
|
||||||
|
| 구분 | `ASDOCSE` | `useCodeList("ASDOCSE")` 등 | 기존 관리 화면과 동일 |
|
||||||
|
| 진행상태 | `{docSeComCd}STTS` | `useCodeList(docSeComCd + "STTS")` | 예: `INSTLSTTS`, `FNRSSTTS`, `SEWERSTTS`. 구분 미선택 시 미호출 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. UI 구현 가이드
|
||||||
|
|
||||||
|
### 7-1. 기술 스택·원칙
|
||||||
|
|
||||||
|
- UI: `@common/components` — DatePicker, Select/`CodeSelect`, Button, DataTable, Pagination
|
||||||
|
- 목록: `@tanstack/react-table` + `useDataTable` (기존 `instl-list` / `fnrs-list` 패턴)
|
||||||
|
- 데이터: React Query `useElaprPrgrsSttsMyDocPage(params)`
|
||||||
|
- 검색 조건·페이지: URL `searchParams` 동기화 권장 (`useSearchParamsState` 스킬)
|
||||||
|
- 진행상태 컬럼: `prgrsSttsComNm` 텍스트 렌더링 (단계형 프로그레스 미사용)
|
||||||
|
- 행 클릭: `docSeComCd` + `prgrsSttsComCd` + `rgtr`로 view/edit 분기 (`SEWER`는 `chgCycl` query 포함)
|
||||||
|
|
||||||
|
### 7-2. 앱·파일 배치
|
||||||
|
|
||||||
|
| 구분 | 경로 |
|
||||||
|
| ------------ | -------------------------------------------------------------- |
|
||||||
|
| 라우트 | `app/(main)/(with-base-layout)/my-pbadms-sprt/page.tsx` |
|
||||||
|
| 페이지 | `pages/my-pbadms-sprt/ui/my-pbadms-sprt-list-page.tsx` |
|
||||||
|
| 목록 그리드 | `pages/my-pbadms-sprt/ui/my-pbadms-sprt-list.tsx` |
|
||||||
|
| 경로 해석 | `pages/my-pbadms-sprt/lib/resolve-detail-path.ts` |
|
||||||
|
| 테이블 컬럼 | `pages/my-pbadms-sprt/model/my-pbadms-sprt-table-columns.tsx` |
|
||||||
|
| Entity API | `entities/elapr/api/elapr-prgrs-stts-service.ts` |
|
||||||
|
| Entity Hook | `entities/elapr/api/queries.ts` → `useElaprPrgrsSttsMyDocPage` |
|
||||||
|
| Entity Model | `entities/elapr/model/elapr-prgrs-stts-interface.ts` |
|
||||||
|
|
||||||
|
### 7-3. 영역별 UI 매핑
|
||||||
|
|
||||||
|
| 영역 | 컴포넌트 | 데이터 |
|
||||||
|
| ---- | -------------------------- | ------------------------------------------ |
|
||||||
|
| A | `my-pbadms-sprt-list-page` | 로컬 state → `ElaprMyDocSearchDto` |
|
||||||
|
| B | DataTable + columns | `useElaprPrgrsSttsMyDocPage` → `content[]` |
|
||||||
|
|
||||||
|
### 7-4. 다이얼로그·라우팅
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ----------- | -------------------------------------------------------------------- |
|
||||||
|
| 다이얼로그 | 없음 (목록 전용) |
|
||||||
|
| 목록 라우트 | `/my-pbadms-sprt` |
|
||||||
|
| 상세 이동 | §5-3 표와 동일 (view/edit + `rgtr` 소유자 검사, SEWER `chgCycl`) |
|
||||||
|
|
||||||
|
### 7-5. 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입
|
||||||
|
SET 기간 = 최근 1개월
|
||||||
|
CALL findMyElaperDocList
|
||||||
|
DISPLAY 영역 B
|
||||||
|
|
||||||
|
ON [검색] CLICK DO
|
||||||
|
CALL findMyElaperDocList // → 영역 B 갱신
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 페이지 CHANGE DO
|
||||||
|
CALL findMyElaperDocList
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 B 행 CLICK DO
|
||||||
|
IF NOT docNo OR unsupported docSeComCd THEN
|
||||||
|
// no-op
|
||||||
|
ELSE IF canEdit(prgrsSttsComCd, rgtr === userUnqId) THEN
|
||||||
|
NAVIGATE edit 경로 // SEWER면 ?chgCycl=
|
||||||
|
ELSE
|
||||||
|
NAVIGATE view 경로
|
||||||
|
END IF
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
# 화면설계서: 알림이력
|
||||||
|
|
||||||
|
## 1. 기본 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ----------- | -------------------------------------------- |
|
||||||
|
| 버전 | 1.4 |
|
||||||
|
| 프로세스명 | UI설계 |
|
||||||
|
| 화면명 | 알림이력 |
|
||||||
|
| 화면 ID | AS_SC0205011 |
|
||||||
|
| 요구사항 ID | NSUR-001-001 |
|
||||||
|
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
|
||||||
|
| 메뉴 경로 | 메인 > 행정지원서비스 > 알림이력 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 화면 목적
|
||||||
|
|
||||||
|
로그인한 사용자가 **본인에게 발송된 행정지원(ADSS) 알림**을 기간·구분으로 조회한다. 목록의 처리 버튼으로 해당 알림을 읽음 처리한 뒤, 연계된 업무 화면으로 이동한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 구성
|
||||||
|
|
||||||
|
### 3-1. 전체 레이아웃
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 브레드크럼: 메인 > 행정지원서비스 > 알림이력 │
|
||||||
|
│ 페이지 타이틀: 알림이력 │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 A] 검색 필터 │
|
||||||
|
│ 기간 [시작일] ~ [종료일] 구분 ▼ [검색] │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 B] 알림 이력 목록 │
|
||||||
|
│ 타이틀: 알림 이력 목록 N개 ▼ │
|
||||||
|
│ ┌────┬──────────────┬────────────────┬──────────┬────────┬────────┐ │
|
||||||
|
│ │ No │ 알림일시 │ 알림내용 │ 연계업무 │ 상태 │ 처리 │ │
|
||||||
|
│ ├────┼──────────────┼────────────────┼──────────┼────────┼────────┤ │
|
||||||
|
│ │ … │ regDt │ notiTtl │ docSeComNm│ Badge │ 버튼 │ │
|
||||||
|
│ └────┴──────────────┴────────────────┴──────────┴────────┴────────┘ │
|
||||||
|
│ [페이지네이션] │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3-2. 화면 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입
|
||||||
|
// 기간 기본값: 오늘 기준 최근 1개월 (시작일 = today-1M, 종료일 = today)
|
||||||
|
CALL findMyAsNotiList(NotiSearchDto + pageable)
|
||||||
|
DISPLAY 알림 이력 목록 그리드 (영역 B)
|
||||||
|
|
||||||
|
ON [검색] 버튼 CLICK DO
|
||||||
|
CALL findMyAsNotiList(영역 A 조건 + page=0)
|
||||||
|
DISPLAY 알림 이력 목록 그리드 (영역 B)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 페이지네이션 CHANGE DO
|
||||||
|
CALL findMyAsNotiList(영역 A 조건 + page/size)
|
||||||
|
DISPLAY 알림 이력 목록 그리드 (영역 B)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 B [처리] 버튼 CLICK DO
|
||||||
|
CALL readNoti(notiId)
|
||||||
|
// UI설계: 해당 알림에 연계된 URL로 이동
|
||||||
|
NAVIGATE notiUrl
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 영역 A — 검색 필터
|
||||||
|
|
||||||
|
구성: **기간 · 구분** (+ 검색).
|
||||||
|
|
||||||
|
서버 `findMyAsNotiList` 고정 정책 (클라이언트 미전달):
|
||||||
|
|
||||||
|
- `recvUnqId` = 로그인 사용자 UUID
|
||||||
|
- `delYn` = `N`
|
||||||
|
- `notiType` = `ADSS` (행정지원 전용 API)
|
||||||
|
|
||||||
|
### 4-1. 필드 정의
|
||||||
|
|
||||||
|
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||||
|
| ---------- | ---------- | ---- | --------------------------- | ----------------- | ------------------------------------------------------------------------------------------------ |
|
||||||
|
| 기간(시작) | DatePicker | N | 오늘 − 1개월 (`YYYY-MM-DD`) | `startDt` | `NotiSearchDto.startDt` |
|
||||||
|
| 기간(종료) | DatePicker | N | 오늘 (`YYYY-MM-DD`) | `endDt` | `NotiSearchDto.endDt` |
|
||||||
|
| 구분 | Select | N | 전체(미선택) | `docSeComCd` | `INSTL` / `FNRS` / `SEWER` / `CNFM` / `PWRK` (또는 단축 `I`/`F`/`S`/`C`/`P`). 공통코드 `ASDOCSE` |
|
||||||
|
|
||||||
|
### 4-2. 버튼
|
||||||
|
|
||||||
|
| 버튼명 | 동작 |
|
||||||
|
| ------ | -------------------------------------------------------------- |
|
||||||
|
| 검색 | 영역 A 조건으로 `findMyAsNotiList` 호출 → 영역 B 갱신 (page=0) |
|
||||||
|
|
||||||
|
### 4-3. 콤보·기본값 주의
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| -------------- | ---------------------------------------------------------- |
|
||||||
|
| 구분 = 전체 | `docSeComCd` 미전달. `notiType`은 서버가 `ADSS`로 고정 |
|
||||||
|
| 구분 옵션 소스 | 공통코드 `ASDOCSE` (`docSeComCd` ↔ `docSeComNm`) |
|
||||||
|
| 기간 | UI설계 상세설명: 기본 최근 1개월. `startDt`/`endDt`로 전달 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 영역 B — 알림 이력 목록
|
||||||
|
|
||||||
|
### 5-1. 그리드 컬럼
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| -------- | ------------ | --------------------------------------------------------------- |
|
||||||
|
| No | (row index) | 페이지 오프셋 반영 |
|
||||||
|
| 알림일시 | `regDt` | 표시 `YYYY-MM-DD HH:mm` (응답 `yyyy-MM-dd HH:mm:ss`) |
|
||||||
|
| 알림내용 | `notiTtl` | 제목 문자열. UI 샘플은 `[YYYY-MM-DD] …` 접두 |
|
||||||
|
| 연계업무 | `docSeComNm` | 문서구분 공통코드명 (`docSeComCd` + `@CodeName` `ASDOCSE`) |
|
||||||
|
| 상태 | (derived) | `Badge` 렌더링. 판정 규칙은 §5-1-1 |
|
||||||
|
| 처리 | (action) | 상태별 버튼 라벨. 클릭 시 `readNoti` 후 `notiUrl`로 이동 (§5-3) |
|
||||||
|
|
||||||
|
#### 5-1-1. 상태 UI 케이스 (`@common/components` `Badge`)
|
||||||
|
|
||||||
|
판정에 사용: `notiPrcsCmptnYn`, `notReadYn`, `regDt` (오늘 기준 경과일).
|
||||||
|
|
||||||
|
| 조건 | 라벨 | Badge |
|
||||||
|
| ------------------------------------------------------------------------------ | -------- | ----------------------------- |
|
||||||
|
| `notiPrcsCmptnYn` = `Y` | 처리완료 | `variant="default"` (primary) |
|
||||||
|
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `Y` 이고 `regDt`로부터 **7일 초과** | 처리지연 | `color="red"` |
|
||||||
|
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `Y` 이고 `regDt`로부터 **7일 이내** | 처리중 | `color="green"` |
|
||||||
|
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `N` 이고 `regDt`로부터 **3일 초과** | 지연 | `color="orange"` |
|
||||||
|
| `notiPrcsCmptnYn` = `N` 이고 `notReadYn` = `N` 이고 `regDt`로부터 **3일 이내** | 미조회 | `color="yellow"` |
|
||||||
|
|
||||||
|
```
|
||||||
|
IF notiPrcsCmptnYn = Y THEN
|
||||||
|
DISPLAY Badge "처리완료" (primary / variant=default)
|
||||||
|
ELSE IF notReadYn = Y THEN
|
||||||
|
IF daysSince(regDt) > 7 THEN
|
||||||
|
DISPLAY Badge "처리지연" (red)
|
||||||
|
ELSE
|
||||||
|
DISPLAY Badge "처리중" (green)
|
||||||
|
END IF
|
||||||
|
ELSE // notReadYn = N
|
||||||
|
IF daysSince(regDt) > 3 THEN
|
||||||
|
DISPLAY Badge "지연" (orange)
|
||||||
|
ELSE
|
||||||
|
DISPLAY Badge "미조회" (yellow)
|
||||||
|
END IF
|
||||||
|
END IF
|
||||||
|
```
|
||||||
|
|
||||||
|
- `daysSince(regDt)`: 오늘 날짜 − `regDt` 일자 (일 단위)
|
||||||
|
- `regDt`가 없으면 상태 Badge 표시 전략 TBD (미표시 또는 기본값)
|
||||||
|
|
||||||
|
응답에 있으나 그리드에 **표시하지 않음**:
|
||||||
|
|
||||||
|
| Field | 비고 |
|
||||||
|
| ----------------- | ---------------------------------------------------------------------- |
|
||||||
|
| `notiId` | 알림 ID — 행 키 |
|
||||||
|
| `recvUnqId` | 수신 회원 UUID (서버가 로그인 사용자로 고정) |
|
||||||
|
| `senderUnqId` | 발신 회원 UUID |
|
||||||
|
| `notiType` | 알림 종류 (`ADSS`) |
|
||||||
|
| `notiCn` | 알림 내용 (목록에서는 서버가 제외할 수 있음) |
|
||||||
|
| `notReadYn` | 확인 여부 `Y`/`N`. 상태 Badge 판정에 사용 (§5-1-1) |
|
||||||
|
| `notiUrl` | 처리 버튼 이동 URL. 컬럼 미표시 |
|
||||||
|
| `notiLnkgUnqNo` | 알림연결고유번호 |
|
||||||
|
| `docSeComCd` | 문서구분 공통코드. 연계업무 표시는 `docSeComNm` 사용 |
|
||||||
|
| `notiIdntyDt` | 알림 확인 일시 |
|
||||||
|
| `notiIdntyYn` | 알림 확인 여부 (`notReadYn`과 동일 의미). 상태 판정은 `notReadYn` 사용 |
|
||||||
|
| `notiPrcsCmptnYn` | 알림 처리 완료 여부. 상태 Badge 판정에 사용 (§5-1-1) |
|
||||||
|
| `notiPrcsCmptnDt` | 알림 처리 완료 일시 |
|
||||||
|
|
||||||
|
### 5-2. 목록 메타·페이지네이션
|
||||||
|
|
||||||
|
| 항목 | 매핑 |
|
||||||
|
| ------------- | ------------------------------------------- |
|
||||||
|
| 건수 / size ▼ | 상단 우측 `N개` 셀렉트 → `Pageable.size` |
|
||||||
|
| page / size | `NotiSearchDto`의 `PageableParams` |
|
||||||
|
| 페이지 컨트롤 | Spring `Page` 표준 (`number`, `totalPages`) |
|
||||||
|
|
||||||
|
### 5-3. 행 상호작용
|
||||||
|
|
||||||
|
| 동작 | 설명 |
|
||||||
|
| -------------- | --------------------------------------------------------------------------- |
|
||||||
|
| 처리 버튼 클릭 | `readNoti(notiId)`로 읽음 처리 후, 해당 알림에 연계된 URL(`notiUrl`)로 이동 |
|
||||||
|
| 행 클릭 | UI설계에 없음. 처리 버튼만 명시 |
|
||||||
|
|
||||||
|
**처리 버튼 라벨** (상태 Badge §5-1-1 기준):
|
||||||
|
|
||||||
|
| 상태 | 버튼명 |
|
||||||
|
| -------- | -------- |
|
||||||
|
| 미조회 | 처리하기 |
|
||||||
|
| 지연 | 처리하기 |
|
||||||
|
| 처리중 | 처리하기 |
|
||||||
|
| 처리지연 | 처리하기 |
|
||||||
|
| 처리완료 | 상세보기 |
|
||||||
|
|
||||||
|
- `notiUrl`이 비어 있으면 이동하지 않음 (no-op). 읽음 처리는 `notiUrl` 유무와 관계없이 호출
|
||||||
|
- 읽음 처리 성공 후 목록 쿼리(`notiBaseKey`)를 invalidate 하여 영역 B 상태를 갱신
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. API/DTO 명세
|
||||||
|
|
||||||
|
### 6-1. 공통
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
| --------------- | ---------------------------------------------------------------------------------------- |
|
||||||
|
| Base prefix | `/cms-service/api/v1/noti` (`ENDPOINT.CMS_SERVICE.NOTI`) |
|
||||||
|
| 인증 | 로그인 사용자 UUID — 서버 `SecurityUtils.getLoggedInUserUnqIdUUID()` (클라이언트 미전달) |
|
||||||
|
| 클라이언트 모듈 | `@/entities/noti` — `notiService` / `useFindMyAsNotiList` / `useReadNoti` |
|
||||||
|
|
||||||
|
### 6-2. 로그인한 사용자 행정지원 알림 목록 — `findMyAsNotiList`
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| ---------------------------------- | ------ | ------------------- | --------------------------------- | ------------------------------------- | ------------------------------------------------------ |
|
||||||
|
| 로그인한 사용자 행정지원 알림 목록 | GET | `{BASE}/as/my/list` | Query: `NotiSearchDto` + Pageable | `PageResponse<NotiAsUserResponseDto>` | `notiService.findMyAsNotiList` / `useFindMyAsNotiList` |
|
||||||
|
|
||||||
|
**Request (`NotiSearchDto` + Pageable)**
|
||||||
|
|
||||||
|
| 필드 | 필수 | 설명 |
|
||||||
|
| ------------------------ | ---- | --------------------------------------------------------------------------- |
|
||||||
|
| `recvUnqId` | N | 수신 회원 UUID. 서버가 로그인 사용자로 덮어씀 |
|
||||||
|
| `senderUnqId` | N | 발신 회원 UUID. 본 화면 필터에 없음 |
|
||||||
|
| `notiType` | N | 알림 종류. 본 API는 서버가 `ADSS`로 고정 |
|
||||||
|
| `notiTtlKeyword` | N | 알림 제목 키워드. 본 화면 필터에 없음 |
|
||||||
|
| `notiCnKeyword` | N | 알림 내용 키워드. 본 화면 필터에 없음 |
|
||||||
|
| `notReadYn` | N | 알림 확인 여부 (`Y`/`N`). 본 화면 검색 필터에 없음 |
|
||||||
|
| `delYn` | N | 삭제 여부. 서버가 `N`으로 고정 |
|
||||||
|
| `startDt` | N | 시작일자 `YYYY-MM-DD` — 영역 A 기간(시작) |
|
||||||
|
| `endDt` | N | 종료일자 `YYYY-MM-DD` — 영역 A 기간(종료) |
|
||||||
|
| `docSeComCd` | N | 문서 구분 공통 코드 (`INSTL`/`FNRS`/`SEWER`/`CNFM`/`PWRK` 등) — 영역 A 구분 |
|
||||||
|
| `page` / `size` / `sort` | N | Spring Pageable |
|
||||||
|
|
||||||
|
**Response (`NotiAsUserResponseDto`)**
|
||||||
|
|
||||||
|
| 필드 | 타입 | 설명 |
|
||||||
|
| ----------------- | ------- | ------------------------------------------------------- |
|
||||||
|
| `notiId` | number | 알림 ID |
|
||||||
|
| `recvUnqId` | string | 수신 회원 UUID |
|
||||||
|
| `senderUnqId` | string | 발신 회원 UUID |
|
||||||
|
| `notiType` | string | 알림 종류 (`ADSS`) |
|
||||||
|
| `notiTtl` | string | 알림 제목 |
|
||||||
|
| `notiCn` | string? | 알림 내용 (목록에서 제외될 수 있음) |
|
||||||
|
| `notReadYn` | string? | 알림 확인 여부 (`Y`/`N`). 상태 Badge 판정 |
|
||||||
|
| `regDt` | string? | 등록 일시 `yyyy-MM-dd HH:mm:ss`. 상태 Badge 경과일 기준 |
|
||||||
|
| `notiUrl` | string? | 알림 URL 주소 |
|
||||||
|
| `notiLnkgUnqNo` | string? | 알림연결고유번호 |
|
||||||
|
| `docSeComCd` | string? | 문서구분 공통코드 |
|
||||||
|
| `docSeComNm` | string? | 문서구분 공통코드명 (`ASDOCSE`) |
|
||||||
|
| `notiIdntyDt` | string? | 알림 확인 일시 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
| `notiIdntyYn` | string? | 알림 확인 여부 (`Y`/`N`) |
|
||||||
|
| `notiPrcsCmptnYn` | string? | 알림 처리 완료 여부 (`Y`/`N`). 상태 Badge 판정 |
|
||||||
|
| `notiPrcsCmptnDt` | string? | 알림 처리 완료 일시 `yyyy-MM-dd HH:mm:ss` |
|
||||||
|
|
||||||
|
### 6-3. 알림 읽음 처리 — `readNoti`
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| -------------- | ------ | ---------------------- | ----------------------- | ---------------- | -------------------------------------- |
|
||||||
|
| 알림 읽음 처리 | PUT | `{BASE}/{notiId}/read` | Path: `notiId` (`Long`) | `Long` (알림 ID) | `notiService.readNoti` / `useReadNoti` |
|
||||||
|
|
||||||
|
**Request**
|
||||||
|
|
||||||
|
| 필드 | 위치 | 필수 | 설명 |
|
||||||
|
| -------- | ---- | ---- | ------- |
|
||||||
|
| `notiId` | Path | Y | 알림 ID |
|
||||||
|
|
||||||
|
Body 없음.
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
| 타입 | 설명 |
|
||||||
|
| ------ | ------------------------------ |
|
||||||
|
| number | 읽음 처리된 알림 ID (`notiId`) |
|
||||||
|
|
||||||
|
화면 매핑: 영역 B [처리] 버튼 클릭 시 호출. 성공 시 `notiBaseKey` invalidate → `findMyAsNotiList` 재조회.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. UI 구현 가이드
|
||||||
|
|
||||||
|
### 7-1. 기술 스택·원칙
|
||||||
|
|
||||||
|
- UI: `@common/components` — DatePicker, Select, Button, DataTable, Pagination, **Badge**
|
||||||
|
- 목록: `@tanstack/react-table` + `useDataTable` (기존 `my-pbadms-sprt-list` 패턴)
|
||||||
|
- 데이터: React Query `useFindMyAsNotiList(params)`, `useReadNoti()`
|
||||||
|
- 검색 조건·페이지: URL `searchParams` 동기화 권장 (`useSearchParamsState` 스킬)
|
||||||
|
- 처리 컬럼: 행 클릭이 아니라 **버튼 클릭**으로 `readNoti` 후 `notiUrl` 이동
|
||||||
|
- 상태 컬럼: §5-1-1 규칙으로 `Badge` 렌더링 (`notiPrcsCmptnYn` / `notReadYn` / `regDt`)
|
||||||
|
- 연계업무: `docSeComNm` 표시
|
||||||
|
|
||||||
|
### 7-2. 앱·파일 배치
|
||||||
|
|
||||||
|
| 구분 | 경로 |
|
||||||
|
| ------------ | --------------------------------------------------------------------- |
|
||||||
|
| 라우트 | `app/(main)/(with-base-layout)/noti/page.tsx` |
|
||||||
|
| 페이지 | `pages/noti/ui/noti-list-page.tsx` |
|
||||||
|
| 목록 그리드 | `pages/noti/ui/noti-list.tsx` |
|
||||||
|
| 테이블 컬럼 | `pages/noti/model/noti-table-columns.tsx` |
|
||||||
|
| 상태 판정 | `pages/noti/lib/resolve-noti-status.ts` |
|
||||||
|
| Entity API | `entities/noti/api/noti-service.ts` |
|
||||||
|
| Entity Hook | `entities/noti/api/queries.ts` → `useFindMyAsNotiList`, `useReadNoti` |
|
||||||
|
| Entity Model | `entities/noti/model/noti-interface.ts` |
|
||||||
|
|
||||||
|
화면 레이어(`pages/noti`)·Entity(`findMyAsNotiList`, `readNoti`) 구현 완료.
|
||||||
|
|
||||||
|
### 7-3. 영역별 UI 매핑
|
||||||
|
|
||||||
|
| 영역 | 컴포넌트 | 데이터 |
|
||||||
|
| ---- | ------------------- | -------------------------------------------------------------- |
|
||||||
|
| A | `noti-list-page` | 로컬 state → `NotiSearchDto` |
|
||||||
|
| B | DataTable + columns | `useFindMyAsNotiList` → `content[]`. 처리 버튼 → `useReadNoti` |
|
||||||
|
|
||||||
|
### 7-4. 다이얼로그·라우팅
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ----------- | --------------------------------------------------------------------- |
|
||||||
|
| 다이얼로그 | 없음 (목록 전용) |
|
||||||
|
| 목록 라우트 | `/noti` |
|
||||||
|
| 상세 이동 | 처리 버튼 → `readNoti(notiId)` 후 `notiUrl` (절대/상대 경로 형식 TBD) |
|
||||||
|
|
||||||
|
### 7-5. 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 최초 진입
|
||||||
|
SET 기간 = 최근 1개월 // startDt / endDt 전달
|
||||||
|
CALL findMyAsNotiList
|
||||||
|
DISPLAY 영역 B
|
||||||
|
|
||||||
|
ON [검색] CLICK DO
|
||||||
|
CALL findMyAsNotiList // → 영역 B 갱신
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 페이지 CHANGE DO
|
||||||
|
CALL findMyAsNotiList
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 영역 B [처리] 버튼 CLICK DO
|
||||||
|
CALL readNoti(notiId) // → 영역 B 상태 갱신 (invalidate)
|
||||||
|
IF NOT notiUrl THEN
|
||||||
|
// no-op (읽음 처리는 수행됨)
|
||||||
|
ELSE
|
||||||
|
NAVIGATE notiUrl
|
||||||
|
END IF
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
# 화면설계서: 이전기본계획조회 (기본 계획 찾기)
|
||||||
|
|
||||||
|
## 1. 기본 정보
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ---------- | --------------------------------------------------------------- |
|
||||||
|
| 버전 | 0.2 |
|
||||||
|
| 프로세스명 | UI설계 |
|
||||||
|
| 화면명 | 이전기본계획조회 (기본 계획 찾기) |
|
||||||
|
| 화면 유형 | [v] 조회 [ ] 입력 [ ] 수정 [ ] 삭제 [ ] 출력 |
|
||||||
|
| 메뉴 경로 | 하수도기본계획 등록·수정 > 사업개요 > [이전기본계획조회] Dialog |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 화면 목적
|
||||||
|
|
||||||
|
하수도기본계획 등록·수정 화면에서 **이전(기수립) 기본계획** 수치와 관련 보고서를 **조회·다운로드**한다. 시도·구군·연도·처리시설을 선택한 뒤 총설·처리구역별 현황을 표시하고, 분류 트리에서 기본계획 파일을 내려받는다. **부모 사업개요 폼에는 수치를 반영하지 않는다**(레거시 `bscPlnPopup.jsp`와 동일).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 화면 구성
|
||||||
|
|
||||||
|
### 3-1. 전체 레이아웃
|
||||||
|
|
||||||
|
부모: 하수도기본계획 등록/수정 폼 → 사업개요 섹션의 `[이전기본계획조회]` 버튼으로 Dialog 오픈.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Dialog 타이틀: 기본 계획 찾기 [닫기] │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 A] 검색 필터 │
|
||||||
|
│ 시도 ▼ 구군 ▼ 연도 ▼ 처리시설 ▼ [조회] │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 B] 기본계획파일 │
|
||||||
|
│ ┌──────────────────────┬────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ [영역 B-1] 분류 트리 │ [영역 B-2] 파일 목록 │ │
|
||||||
|
│ │ (약 40%) │ (약 60%) │ │
|
||||||
|
│ │ └ 노드… │ 파일명 / 다운로드 │ │
|
||||||
|
│ └──────────────────────┴────────────────────────────────────────────────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 C] 총설 │
|
||||||
|
│ ┌────────┬──────────────당초──────────────┬──기준──┬──────────────변경──────────────┐ │
|
||||||
|
│ │ 구분 │ 1단계 │ 2단계 │ 3단계 │ 4단계 │ 기준 │ 1단계 │ 2단계 │ 3단계 │ 4단계 │ │
|
||||||
|
│ ├────────┼───────┼───────┼───────┼───────┼────────┼───────┼───────┼───────┼───────┤ │
|
||||||
|
│ │계획목표│ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ 년 │ │
|
||||||
|
│ │년도 │ │ │ │ │ │ │ │ │ │ │
|
||||||
|
│ └────────┴───────┴───────┴───────┴───────┴────────┴───────┴───────┴───────┴───────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ [영역 D] 처리구역별 현황 │
|
||||||
|
│ ┌────────┬──────────────당초──────────────┬──기준──┬──────────────변경──────────────┐ │
|
||||||
|
│ │ 구분 │ 1~4단계(+연도헤더) │ 기준 │ 1~4단계(+연도헤더) │ │
|
||||||
|
│ ├────────┼────────────────────────────────┼────────┼────────────────────────────────┤ │
|
||||||
|
│ │ 하수처리인구 … 배제방식 (읽기 전용 행) │ │
|
||||||
|
│ └────────┴────────────────────────────────┴────────┴────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3-2. 화면 데이터 흐름
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
// 부모 폼 — 사업개요
|
||||||
|
ON [이전기본계획조회] 버튼 CLICK DO
|
||||||
|
OPEN PreviousPlanLookupDialog
|
||||||
|
// 프리필: 부모 폼 sewerBscPlan.sido / gugun / admdstCd
|
||||||
|
CALL findSidoCombo()
|
||||||
|
DISPLAY 시도 옵션 (영역 A)
|
||||||
|
IF 시도 프리필 THEN
|
||||||
|
CALL findGugunCombo(sidoCd)
|
||||||
|
DISPLAY 구군 옵션 (영역 A)
|
||||||
|
CALL getLvNowDmgMtncGnsvy({ level: "1", ctpvNm })
|
||||||
|
// TBD: useLvNowDmgMtncGnsvy enabled — level별 필수 파라미터만 검사하도록 조정
|
||||||
|
IF 구군 프리필 THEN
|
||||||
|
CALL getLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })
|
||||||
|
DISPLAY 연도 옵션 (영역 A) // children 중 level=3 노드의 gnsvyYr
|
||||||
|
CALL getClsfSystm({ clsfSystmNm: gugunNm })
|
||||||
|
DISPLAY 분류 트리 (영역 B-1)
|
||||||
|
END IF
|
||||||
|
END IF
|
||||||
|
END ON
|
||||||
|
|
||||||
|
// 영역 A: 시도 변경
|
||||||
|
ON 영역 A 시도 CHANGE DO
|
||||||
|
RESET 구군 / 연도 / 처리시설
|
||||||
|
CALL findGugunCombo(sidoCd)
|
||||||
|
CALL getLvNowDmgMtncGnsvy({ level: "1", ctpvNm })
|
||||||
|
// 구군 미선택 → 연도·처리시설 옵션 비움, 트리 빈 안내
|
||||||
|
END ON
|
||||||
|
|
||||||
|
// 영역 A: 구군 변경
|
||||||
|
ON 영역 A 구군 CHANGE DO
|
||||||
|
RESET 연도 / 처리시설
|
||||||
|
CALL getLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })
|
||||||
|
DISPLAY 연도 옵션 (영역 A) // level=3 노드 → gnsvyYr
|
||||||
|
CALL getClsfSystm({ clsfSystmNm: sggNm })
|
||||||
|
DISPLAY 분류 트리 (영역 B-1)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
// 영역 A: 연도 변경
|
||||||
|
ON 영역 A 연도 CHANGE DO
|
||||||
|
RESET 처리시설
|
||||||
|
CALL getLvNowDmgMtncGnsvy({ level: "3", ctpvNm, sggNm, gnsvyYr })
|
||||||
|
DISPLAY 처리시설 옵션 (영역 A) // level=3 노드 children → swtrmtFcltCd / swtrmtFcltNm
|
||||||
|
END ON
|
||||||
|
|
||||||
|
// 영역 A: 조회
|
||||||
|
ON [조회] 버튼 CLICK DO
|
||||||
|
VALIDATE 시도, 구군, 연도(targetYear), 처리시설(swtrmtFcltCd)
|
||||||
|
CALL getNowDmgMtncGnsvy({ swtrmtFcltCd, ctpvNm, sggNm, targetYear })
|
||||||
|
DISPLAY 총설 (영역 C) // 응답[0]의 planYr / planYrDt* / planYrDc*
|
||||||
|
DISPLAY 처리구역별 현황 (영역 D) // 응답[0]의 지표 Dt1~4 / base / Dc1~4
|
||||||
|
END ON
|
||||||
|
|
||||||
|
// 영역 B: 트리 노드 선택
|
||||||
|
ON 영역 B-1 노드 CLICK DO
|
||||||
|
CALL getFile({ clsfSystmSn, sort, vetPrcsYn }) // Response DTO TBD
|
||||||
|
DISPLAY 파일 목록 (영역 B-2)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
// 영역 B: 파일 다운로드
|
||||||
|
ON 영역 B-2 파일 CLICK DO
|
||||||
|
CALL getFilePrcs(fileSn) // 파일 메타(경로·제목) — Response TBD
|
||||||
|
CALL fileDownload({ filePath, fileName })
|
||||||
|
// 브라우저 다운로드 트리거
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON [닫기] DO
|
||||||
|
CLOSE PreviousPlanLookupDialog
|
||||||
|
// 부모 폼 값 변경 없음
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 영역 A — 검색 필터
|
||||||
|
|
||||||
|
구성: **시도 · 구군 · 연도 · 처리시설** (+ 조회).
|
||||||
|
|
||||||
|
- 시도·구군: `adstdstr` 콤보 (기존 스펙 유지)
|
||||||
|
- 연도·처리시설: 각각 `useLvNowDmgMtncGnsvy`를 **별도 호출**. `level`에 따라 반환 계층이 달라진다.
|
||||||
|
|
||||||
|
### 4-1. 필드 정의
|
||||||
|
|
||||||
|
| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 |
|
||||||
|
| -------- | --------- | ---- | ------------------------- | ------------------------------------------------ | -------------------------------------------------------------- |
|
||||||
|
| 시도 | Select | Y | 부모 `sewerBscPlan.sido` | `ctpvNm`(표시명), 코드는 `adstDstrCd` | `useFindSidoCombo` |
|
||||||
|
| 구군 | Select | Y | 부모 `sewerBscPlan.gugun` | `sggNm`(표시명), 코드는 `adstDstrCd` | `useFindGugunCombo(sidoCd)` |
|
||||||
|
| 연도 | Select | Y | 없음 | `gnsvyYr` / 조회 시 `targetYear` | `useLvNowDmgMtncGnsvy({ level: "2", ctpvNm, sggNm })` |
|
||||||
|
| 처리시설 | Select | Y | 없음 | `swtrmtFcltCd` ← 옵션 value, 라벨 `swtrmtFcltNm` | `useLvNowDmgMtncGnsvy({ level: "3", ctpvNm, sggNm, gnsvyYr })` |
|
||||||
|
|
||||||
|
> 처리시설은 `bns-if` `useFacilityList`를 **사용하지 않는다**.
|
||||||
|
|
||||||
|
### 4-2. 버튼
|
||||||
|
|
||||||
|
| 버튼명 | 동작 |
|
||||||
|
| ------ | -------------------------------------------------------- |
|
||||||
|
| 조회 | 필수값 검증 후 `getNowDmgMtncGnsvy` 호출 → 영역 C·D 갱신 |
|
||||||
|
| 닫기 | Dialog 종료 (부모 미반영) |
|
||||||
|
|
||||||
|
### 4-3. `lvNowDmgMtncGnsvy` level 캐스케이드
|
||||||
|
|
||||||
|
| 트리거 | Request `level` | Request 파라미터 | UI에 쓰는 응답 | 옵션 추출 |
|
||||||
|
| --------- | --------------- | ---------------------------- | --------------- | ------------------------------------------------------------------------------ |
|
||||||
|
| 시도 선택 | `"1"` | `ctpvNm` | (하위 초기화용) | 연도·처리시설 Select 리셋. 구군은 `findGugunCombo` |
|
||||||
|
| 구군 선택 | `"2"` | `ctpvNm`, `sggNm` | 연도 Select | 트리에서 `level === 3` 노드의 `gnsvyYr` |
|
||||||
|
| 연도 선택 | `"3"` | `ctpvNm`, `sggNm`, `gnsvyYr` | 처리시설 Select | 선택 연도(`level === 3`) 노드의 `children[]` → `swtrmtFcltCd` / `swtrmtFcltNm` |
|
||||||
|
|
||||||
|
상위 변경 시 하위 선택값·옵션을 RESET한다 (시도→구군·연도·시설 / 구군→연도·시설 / 연도→시설).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 영역 B — 기본계획파일
|
||||||
|
|
||||||
|
### 5-1. 트리 (B-1)
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------ | --------------- | ------------------------ |
|
||||||
|
| 분류명 | `clsfSystmNm` | `ClsfSystmResponseDto` |
|
||||||
|
| 노드키 | `clsfSystmSn` | 선택 시 파일 목록 조회 |
|
||||||
|
| 상위키 | `upClsfSystmSn` | 클라이언트에서 트리 조립 |
|
||||||
|
| 정렬 | `sortSeq` | |
|
||||||
|
| 경로 | `path` | |
|
||||||
|
|
||||||
|
- 조회 파라미터: `clsfSystmNm` = 선택 구군명(레거시와 동일). `upClsfSystmSn`은 하위 전개 시 TBD.
|
||||||
|
- 시도·구군 미선택 시: 「시도와 구군을 선택하세요.」 안내.
|
||||||
|
|
||||||
|
### 5-2. 파일 목록 (B-2)
|
||||||
|
|
||||||
|
| 표시명 | Field | 비고 |
|
||||||
|
| ------ | ----- | ------------------------------------------------------------------- |
|
||||||
|
| 파일명 | TBD | `getFile` Response DTO가 `unknown` — 백엔드 스키마 확정 후 매핑 |
|
||||||
|
| (기타) | TBD | 레거시: 트리 선택 후 목록 표시, 클릭 시 `fileprcs` → `fileDownload` |
|
||||||
|
|
||||||
|
### 5-3. 버튼·상호작용
|
||||||
|
|
||||||
|
| 동작 | 설명 |
|
||||||
|
| -------------- | -------------------------------------------------------------- |
|
||||||
|
| 트리 노드 클릭 | `getFile({ clsfSystmSn, … })` → B-2 목록 |
|
||||||
|
| 파일 클릭 | `getFilePrcs(fileSn)` → `fileDownload({ filePath, fileName })` |
|
||||||
|
| 빈 목록 | 「트리에서 항목을 선택하세요.」 / 「파일이 없습니다.」 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 영역 C — 총설
|
||||||
|
|
||||||
|
### 6-1. 테이블 구조
|
||||||
|
|
||||||
|
| 행(구분) | 열 매핑 (당초1~~4 / 기준 / 변경1~~4) |
|
||||||
|
| ------------ | ------------------------------------------------------------------------------ |
|
||||||
|
| 계획목표년도 | `planYrDt1planYrDt4` ~~/~~ `planYr` ~~/~~ `planYrDc1planYrDc4` (+ `년` 접미사) |
|
||||||
|
|
||||||
|
- 헤더: 당초(1~~4단계) · 기준 · 변경(1~~4단계)
|
||||||
|
- 데이터 소스: `getNowDmgMtncGnsvy` 응답 배열의 **첫 번째** 요소
|
||||||
|
- 읽기 전용
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 영역 D — 처리구역별 현황
|
||||||
|
|
||||||
|
### 7-1. 행 정의 (표시명 → Field 접두사)
|
||||||
|
|
||||||
|
각 행은 `Dt1Dt4` ~~/ (접미사 없음=기준) /~~ `Dc1Dc4` 셀로 표시한다. 숫자는 천단위 콤마, 없으면 `-`.
|
||||||
|
|
||||||
|
| 표시명 | Field 접두사 | 비고 |
|
||||||
|
| ------------------------ | -------------------- | ---------------------------------------------- |
|
||||||
|
| 하수처리인구(인) | `swtrmtPpltnCnt` | |
|
||||||
|
| 자연적하수처리인구수(인) | `natrSwtrmtPpltnCnt` | |
|
||||||
|
| 사회적하수처리인구수(인) | `scilSwtrmtPpltnCnt` | |
|
||||||
|
| 하수처리구역면적 | `swtrmtZoneArea` | |
|
||||||
|
| 물사용량원단위 | `wtrUsqtyOrgnUnit` | |
|
||||||
|
| 오수전환율(%) | `drtwSwtcRt` | |
|
||||||
|
| 변동부하율(%) | `flctnLodRt` | |
|
||||||
|
| 오수원단위 | `drtwOrgnUnit` | |
|
||||||
|
| 계획하수량합계(㎥/일) | `planSwageQntSum` | |
|
||||||
|
| 생활오수(㎥/일) | `lvngDrtwSum` | |
|
||||||
|
| 지하수(㎥/일) | `ugwtSum` | |
|
||||||
|
| 공장폐수(㎥/일) | `fctryWwtSum` | |
|
||||||
|
| 관광오수(㎥/일) | `toursmDrtwSum` | |
|
||||||
|
| 군부대(㎥/일) | `mtbsSum` | |
|
||||||
|
| 연계처리수(㎥/일) | `linkPrcsCntSum` | |
|
||||||
|
| 기타오수(㎥/일) | `etcDrtwSum` | |
|
||||||
|
| 지하수사용량(㎥/일) | `ugwtUseSum` | |
|
||||||
|
| 시설용량(㎥/일) | `fcltCpctSum` | |
|
||||||
|
| 배제방식 | `exclsMethExpln` | 문자열; 단계별 `*Dt*`/`*Dc*` 존재 시 동일 규칙 |
|
||||||
|
|
||||||
|
헤더 3행째 연도: 영역 C와 동일하게 `planYrDt*` / `planYr` / `planYrDc*` 표시.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. API/DTO 명세
|
||||||
|
|
||||||
|
### 8-1. 공통
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
| --------------- | ---------------------------------------------------- |
|
||||||
|
| DC Base | `/dc-service/api/v1` (`ENDPOINT.DC_SERVICE`) |
|
||||||
|
| AS adstdstr | `/as-service/api/v1/adstdstr` |
|
||||||
|
| 클라이언트 모듈 | `@/entities/dc` (`dcService`), `@/entities/adstdstr` |
|
||||||
|
|
||||||
|
> 영역 A 처리시설은 `bns-if`를 사용하지 않음.
|
||||||
|
|
||||||
|
### 8-2. 시도·구군 콤보 (non-DC)
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| -------- | ------ | ----------------------------------- | ------------------------ | -------------------------------- | ---------------------------------------------------------- |
|
||||||
|
| 시도목록 | GET | `/as-service/api/v1/adstdstr/sido` | — | `CmCdAdstDstrComboResponseDto[]` | `cmCdAdstDstrService.findSidoCombo` / `useFindSidoCombo` |
|
||||||
|
| 구군목록 | GET | `/as-service/api/v1/adstdstr/gugun` | Query: `sido` (시도코드) | `CmCdAdstDstrComboResponseDto[]` | `cmCdAdstDstrService.findGugunCombo` / `useFindGugunCombo` |
|
||||||
|
|
||||||
|
### 8-3. 연도·처리시설 계층 조회 — DC
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| --------------- | ------ | -------------------------------------- | ---------------------------- | -------------------------------- | --------------------------------------------------------- |
|
||||||
|
| 계층형 계획목록 | GET | `/dc-service/api/v1/lvNowDmgMtncGnsvy` | `LvNowDmgMtncGnsvySearchDto` | `LvNowDmgMtncGnsvyResponseDto[]` | `dcService.getLvNowDmgMtncGnsvy` / `useLvNowDmgMtncGnsvy` |
|
||||||
|
|
||||||
|
연도 Select·처리시설 Select는 **각각** `useLvNowDmgMtncGnsvy`를 호출한다 (`level`만 다르게).
|
||||||
|
|
||||||
|
**Request (**`LvNowDmgMtncGnsvySearchDto`**)**
|
||||||
|
|
||||||
|
| 필드 | 시도(`level=1`) | 구군→연도(`level=2`) | 연도→시설(`level=3`) |
|
||||||
|
| --------- | --------------- | -------------------- | -------------------- |
|
||||||
|
| `level` | `"1"` | `"2"` | `"3"` |
|
||||||
|
| `ctpvNm` | 필수 | 필수 | 필수 |
|
||||||
|
| `sggNm` | — | 필수 | 필수 |
|
||||||
|
| `gnsvyYr` | — | — | 필수 |
|
||||||
|
|
||||||
|
**Response 노드 필드 (**`LvNowDmgMtncGnsvyResponseDto`**, 부분 optional)**
|
||||||
|
|
||||||
|
| 필드 | 등장 level | 설명 |
|
||||||
|
| -------------- | ---------- | ------------------------ |
|
||||||
|
| `level` | 1~3 | 계층 깊이 |
|
||||||
|
| `ctpvNm` | 1 | 시도명 |
|
||||||
|
| `sggNm` | 2 | 구군명 |
|
||||||
|
| `gnsvyYr` | 3 | 연도 → 연도 Select value |
|
||||||
|
| `swtrmtFcltCd` | 시설(리프) | 처리시설 코드 |
|
||||||
|
| `swtrmtFcltNm` | 시설(리프) | 처리시설명 |
|
||||||
|
| `children` | 1~3 | 하위 노드 배열 |
|
||||||
|
|
||||||
|
> **TBD**: 현재 `useLvNowDmgMtncGnsvy` `enabled`가 `level && ctpvNm && sggNm && gnsvyYr`로 고정되어 `level=1`/`2` 호출이 막힘. level별 필수 파라미터만 검사하도록 수정 필요.
|
||||||
|
>
|
||||||
|
> 예: `level=1` → `ctpvNm` / `level=2` → `ctpvNm && sggNm` / `level=3` → `ctpvNm && sggNm && gnsvyYr`
|
||||||
|
|
||||||
|
### 8-4. 기본계획 수치 조회 — DC
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| ---------------- | ------ | ------------------------------------ | -------------------------- | ------------------------------ | ----------------------------------------------------- |
|
||||||
|
| 현재·단계별 수치 | GET | `/dc-service/api/v1/nowDmgMtncGnsvy` | `NowDmgMtncGnsvySearchDto` | `NowDmgMtncGnsvyResponseDto[]` | `dcService.getNowDmgMtncGnsvy` / `useNowDmgMtncGnsvy` |
|
||||||
|
|
||||||
|
**Request (**`NowDmgMtncGnsvySearchDto`**)**
|
||||||
|
|
||||||
|
| 필드 | 매핑 |
|
||||||
|
| -------------- | ----------------------- |
|
||||||
|
| `swtrmtFcltCd` | 영역 A 처리시설 |
|
||||||
|
| `ctpvNm` | 영역 A 시도 표시명 |
|
||||||
|
| `sggNm` | 영역 A 구군 표시명 |
|
||||||
|
| `targetYear` | 영역 A 연도 (`gnsvyYr`) |
|
||||||
|
|
||||||
|
**Response**: 지표 필드 + `Dt1Dt4` ~~/~~ `Dc1Dc4` 접미사 스테이지 필드 (`NowDmgMtncGnsvyResponseDto`). UI는 `data[0]` 사용.
|
||||||
|
|
||||||
|
### 8-5. 분류체계 — DC
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO | Response DTO | API |
|
||||||
|
| ------------ | ------ | ------------------------------ | -------------------- | ------------------------ | ----------------------------------------- |
|
||||||
|
| 분류트리조회 | GET | `/dc-service/api/v1/clsfSystm` | `ClsfSystmParamsDto` | `ClsfSystmResponseDto[]` | `dcService.getClsfSystm` / `useClsfSystm` |
|
||||||
|
|
||||||
|
**Request**: `clsfSystmNm?`, `upClsfSystmSn?`
|
||||||
|
**Response**: `clsfSystmSn`, `upClsfSystmSn`, `clsfSystmNm`, `sortSeq`, `droppAble`, `path`
|
||||||
|
|
||||||
|
> 레거시 경로 `/clsf` → 현재 클라이언트는 `/clsfSystm`.
|
||||||
|
|
||||||
|
### 8-6. 파일 목록·처리·다운로드 — DC
|
||||||
|
|
||||||
|
| 기능 | Method | URL | Request DTO / Params | Response DTO | API |
|
||||||
|
| ------------ | ------ | --------------------------------- | ------------------------------------------------------- | ----------------- | -------------------------------------------- |
|
||||||
|
| 파일목록 | GET | `/dc-service/api/v1/file` | `FileParamsDto` (`sort?`, `clsfSystmSn?`, `vetPrcsYn?`) | `unknown` **TBD** | `dcService.getFile` / `useFile` |
|
||||||
|
| 파일처리정보 | GET | `/dc-service/api/v1/fileprcs` | Query: `fileSn` | `unknown` **TBD** | `dcService.getFilePrcs` / `useFilePrcs` |
|
||||||
|
| 파일다운로드 | POST | `/dc-service/api/v1/fileDownload` | `FileDownloadParamsDto` (`filePath?`, `fileName?`) | `unknown` **TBD** | `dcService.fileDownload` / `useFileDownload` |
|
||||||
|
|
||||||
|
> **TBD**: 파일 관련 Response 스키마·다운로드 blob/헤더 처리·`fileDownload` POST body vs query 전달 방식은 백엔드/게이트웨이 확인 후 확정.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. UI 구현 가이드
|
||||||
|
|
||||||
|
### 9-1. 기술 스택·원칙
|
||||||
|
|
||||||
|
- UI: `@common/components` — `Dialog`, `Select`, `Button`, `Table`, `Skeleton`
|
||||||
|
- 상태: Dialog 로컬 state + React Query (`entities/dc`, `adstdstr`)
|
||||||
|
- 영역 A 연도·처리시설: `useLvNowDmgMtncGnsvy`를 level별 **두 번**(또는 시도 포함 시 세 번) 호출
|
||||||
|
- 테이블: 읽기 전용 고정 행 → `@tanstack/react-table` / `DataTable` **불필요**
|
||||||
|
- 트리: jsTree 미사용 — 재귀 들여쓰기 트리
|
||||||
|
- 번들: `PreviousPlanLookupDialog`는 outline에서 `next/dynamic` 로드 권장
|
||||||
|
- 부모 폼: **수치 미반영** (조회·다운로드 only)
|
||||||
|
- 1차 구현 시 DC 미연동이면 `@faker-js/faker` mock + TODO → 이후 `dcService` 훅으로 교체
|
||||||
|
|
||||||
|
### 9-2. 앱·파일 배치
|
||||||
|
|
||||||
|
| 구분 | 경로 |
|
||||||
|
| ------------ | ------------------------------------------------------------------------ |
|
||||||
|
| 진입 버튼 | `pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx` |
|
||||||
|
| Dialog | `pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx` |
|
||||||
|
| 필터 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-search-filters.tsx` |
|
||||||
|
| 파일 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-file-section.tsx` |
|
||||||
|
| 총설 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-summary-table.tsx` |
|
||||||
|
| 처리구역 | `pages/sewer-bsc-plan/ui/previous-plan/previous-plan-area-table.tsx` |
|
||||||
|
| 셀 매핑 유틸 | `pages/sewer-bsc-plan/lib/previous-plan-table-utils.ts` |
|
||||||
|
| mock(선택) | `pages/sewer-bsc-plan/lib/previous-plan-mock.ts` |
|
||||||
|
| DC entity | `entities/dc/api/dc-service.ts`, `queries.ts`, `model/dc-interface.ts` |
|
||||||
|
| 레거시 참조 | `adssNewOld/.../cmmn/bscPlnPopup.jsp` |
|
||||||
|
|
||||||
|
### 9-3. 영역별 UI 매핑
|
||||||
|
|
||||||
|
| 영역 | 컴포넌트 | 데이터 |
|
||||||
|
| ---- | ------------------------------ | -------------------------------------------------------------- |
|
||||||
|
| A | `previous-plan-search-filters` | adstdstr + `useLvNowDmgMtncGnsvy`(level 1·2·3) |
|
||||||
|
| B | `previous-plan-file-section` | `useClsfSystm` + `useFile` + `useFilePrcs` + `useFileDownload` |
|
||||||
|
| C | `previous-plan-summary-table` | `useNowDmgMtncGnsvy` / 조회 결과 state |
|
||||||
|
| D | `previous-plan-area-table` | 동일 |
|
||||||
|
|
||||||
|
### 9-4. 다이얼로그·라우팅
|
||||||
|
|
||||||
|
| 항목 | 내용 |
|
||||||
|
| ------------ | ------------------------------------------------------------------------ |
|
||||||
|
| 다이얼로그명 | `PreviousPlanLookupDialog` / 타이틀 `기본 계획 찾기` |
|
||||||
|
| 오픈 | 사업개요 `[이전기본계획조회]` |
|
||||||
|
| 라우트 | 없음 (모달). 부모: `/sewer-bsc-plan/regist`, `/sewer-bsc-plan/[id]/edit` |
|
||||||
|
| 선택 반영 | 없음 |
|
||||||
|
|
||||||
|
### 9-5. 데이터 흐름 (구현 요약)
|
||||||
|
|
||||||
|
```
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
ON [이전기본계획조회] CLICK DO
|
||||||
|
OPEN PreviousPlanLookupDialog (sido/gugun 프리필)
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 시도 CHANGE DO
|
||||||
|
CALL findGugunCombo
|
||||||
|
CALL getLvNowDmgMtncGnsvy(level=1)
|
||||||
|
RESET 연도·처리시설
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 구군 CHANGE DO
|
||||||
|
CALL getLvNowDmgMtncGnsvy(level=2) // → 연도 옵션
|
||||||
|
CALL getClsfSystm
|
||||||
|
RESET 처리시설
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 연도 CHANGE DO
|
||||||
|
CALL getLvNowDmgMtncGnsvy(level=3) // → 처리시설 옵션
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON [조회] CLICK DO
|
||||||
|
VALIDATE
|
||||||
|
CALL getNowDmgMtncGnsvy
|
||||||
|
DISPLAY 영역 C · 영역 D
|
||||||
|
END ON
|
||||||
|
|
||||||
|
ON 트리 노드 / 파일 CLICK DO
|
||||||
|
CALL getFile → getFilePrcs → fileDownload // Response TBD
|
||||||
|
END ON
|
||||||
|
|
||||||
|
END
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 미확정 (TBD) 추적
|
||||||
|
|
||||||
|
| ID | 항목 | 비고 |
|
||||||
|
| --- | -------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||||
|
| T1 | `useLvNowDmgMtncGnsvy` enabled을 level별 필수값으로 분기 | `1`: ctpvNm / `2`: +sggNm / `3`: +gnsvyYr |
|
||||||
|
| T2 | `getFile` / `getFilePrcs` / `fileDownload` Response DTO | 현재 `unknown` |
|
||||||
|
| T3 | 파일 다운로드 UX (blob, Content-Disposition, 게이트웨이) | 레거시 apigateway 직접 호출과 차이 |
|
||||||
|
| T4 | `clsfSystm`의 `upClsfSystmSn` 점진 로드 여부 | flat 목록 vs 계층 API |
|
||||||
|
| T5 | 1차 mock vs 즉시 `dcService` 실연동 | 마이그레이션 플랜은 mock 우선, entity는 준비됨 |
|
||||||
|
| T6 | 부모 폼 수치 반영 | 요구 없음(레거시 동일). 요청 시 별도 스펙 |
|
||||||
|
| T7 | `level=1` 응답 UI 활용 여부 | 시도 선택 시 호출하나 구군은 adstdstr — 응답은 캐시·검증용일 수 있음 |
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
||||||
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT ?? "local",
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
replaysSessionSampleRate: 1.0,
|
|
||||||
replaysOnErrorSampleRate: 1.0,
|
|
||||||
integrations: [Sentry.replayIntegration()],
|
|
||||||
debug: process.env.NEXT_PUBLIC_SENTRY_DEBUG === "true",
|
|
||||||
});
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
export async function register() {
|
|
||||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
||||||
await import("./sentry.server.config");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NEXT_RUNTIME === "edge") {
|
|
||||||
await import("./sentry.edge.config");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const onRequestError = Sentry.captureRequestError;
|
|
||||||
+22
-7
@@ -1,8 +1,16 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
import { AUTH_USER_ID, CLAIM_NAME, PORT, PROXY_HOST, SERVER_API_URL, SITE_ID } from "@/shared/config";
|
import {
|
||||||
|
AUTH_USER_ID,
|
||||||
|
CLAIM_NAME,
|
||||||
|
PORT,
|
||||||
|
PROXY_HOST,
|
||||||
|
SERVER_API_URL,
|
||||||
|
SITE_ID,
|
||||||
|
} from "@/shared/config";
|
||||||
|
|
||||||
const serverApiUrl = process.env.SERVER_API_URL;
|
const serverApiUrl = process.env.SERVER_API_URL;
|
||||||
|
const dcServiceApiUrl = process.env.DC_SERVICE_API_URL;
|
||||||
|
|
||||||
// CSP 헤더 설정 함수 (img-src만)
|
// CSP 헤더 설정 함수 (img-src만)
|
||||||
function addSecurityHeaders(response: NextResponse): NextResponse {
|
function addSecurityHeaders(response: NextResponse): NextResponse {
|
||||||
@@ -22,7 +30,15 @@ export function middleware(req: NextRequest) {
|
|||||||
|
|
||||||
if (pathname.startsWith("/server")) {
|
if (pathname.startsWith("/server")) {
|
||||||
const queryParams = searchParams.toString();
|
const queryParams = searchParams.toString();
|
||||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
|
||||||
|
let serverUrl: string | undefined;
|
||||||
|
if (pathname.includes("/dc-service")) {
|
||||||
|
serverUrl = dcServiceApiUrl;
|
||||||
|
} else {
|
||||||
|
serverUrl = serverApiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUrl = `${serverUrl}${pathname.replace("/server", "")}${
|
||||||
queryParams ? `?${queryParams}` : ""
|
queryParams ? `?${queryParams}` : ""
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
@@ -34,7 +50,6 @@ export function middleware(req: NextRequest) {
|
|||||||
return addSecurityHeaders(response);
|
return addSecurityHeaders(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// tus-js-client PATCH 요청: Location 헤더로 /server 없이 반환된 경로 직접 처리
|
// tus-js-client PATCH 요청: Location 헤더로 /server 없이 반환된 경로 직접 처리
|
||||||
// (예: /attach-service/api/v1/tus/upload/{id})
|
// (예: /attach-service/api/v1/tus/upload/{id})
|
||||||
if (pathname.startsWith("/attach-service")) {
|
if (pathname.startsWith("/attach-service")) {
|
||||||
@@ -56,10 +71,10 @@ export function middleware(req: NextRequest) {
|
|||||||
return addSecurityHeaders(response);
|
return addSecurityHeaders(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (pathname === "/") {
|
if (pathname === "/") {
|
||||||
// const response = NextResponse.redirect(new URL("/dashboard", req.url));
|
const response = NextResponse.redirect(new URL("/dashboard", req.url));
|
||||||
// return addSecurityHeaders(response);
|
return response;
|
||||||
// }
|
}
|
||||||
|
|
||||||
logMessages.push("\n--------------------------------\n");
|
logMessages.push("\n--------------------------------\n");
|
||||||
console.log(logMessages.join("\n"));
|
console.log(logMessages.join("\n"));
|
||||||
|
|||||||
+46
-17
@@ -1,24 +1,53 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
import { withSentryConfig } from "@sentry/nextjs";
|
|
||||||
import path from "path";
|
const subDomain = process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:13000";
|
||||||
|
|
||||||
|
const webhwpCtrlBase = (
|
||||||
|
process.env.NEXT_PUBLIC_WEBHYP_CTRL_URL || "http://hwp.onasys.co.kr/webhwpctrl"
|
||||||
|
).replace(/\/$/, "");
|
||||||
|
|
||||||
|
const webhwpAssetPrefixes = ["js", "hwpsdk", "dist", "commonFrame", "wasm"] as const;
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
|
output: "standalone",
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
transpilePackages: ["@common/assets"],
|
transpilePackages: ["@common/assets"],
|
||||||
outputFileTracingRoot: path.join(__dirname, "../../"),
|
experimental: {
|
||||||
|
globalNotFound: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
async rewrites() {
|
||||||
|
return {
|
||||||
|
beforeFiles: [
|
||||||
|
{
|
||||||
|
source: "/_next/static/:path*",
|
||||||
|
destination: `${subDomain}/_next/static/:path*`,
|
||||||
|
has: [
|
||||||
|
{
|
||||||
|
type: "header",
|
||||||
|
key: "referer",
|
||||||
|
value: "(.*)/cms(.*)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
afterFiles: [
|
||||||
|
{
|
||||||
|
source: "/cms/:path*",
|
||||||
|
destination: `${subDomain}/cms/:path*`,
|
||||||
|
},
|
||||||
|
...webhwpAssetPrefixes.map((prefix) => ({
|
||||||
|
source: `/webhwpctrl/${prefix}/:path*`,
|
||||||
|
destination: `${webhwpCtrlBase}/${prefix}/:path*`,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
source: "/webhwpctrl/file/:path*",
|
||||||
|
destination: `${webhwpCtrlBase}/file/:path*`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fallback: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withSentryConfig(nextConfig, {
|
export default nextConfig;
|
||||||
org: process.env.SENTRY_ORG,
|
|
||||||
project: process.env.SENTRY_PROJECT,
|
|
||||||
silent: true,
|
|
||||||
widenClientFileUpload: true,
|
|
||||||
tunnelRoute: "/monitoring",
|
|
||||||
webpack: {
|
|
||||||
treeshake: {
|
|
||||||
removeDebugLogging: process.env.NEXT_PUBLIC_SENTRY_DEBUG === "true" ? false : true,
|
|
||||||
},
|
|
||||||
automaticVercelMonitors: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,6 @@
|
|||||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||||
"@hookform/resolvers": "^4.1.3",
|
"@hookform/resolvers": "^4.1.3",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@opentelemetry/api": "^1.9.1",
|
|
||||||
"@opentelemetry/context-zone": "^2.8.0",
|
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
|
|
||||||
"@opentelemetry/instrumentation-fetch": "^0.219.0",
|
|
||||||
"@opentelemetry/instrumentation-xml-http-request": "^0.219.0",
|
|
||||||
"@opentelemetry/sdk-trace-web": "^2.8.0",
|
|
||||||
"@sentry/nextjs": "^10.57.0",
|
|
||||||
"@tanstack/react-query": "^5.75.5",
|
"@tanstack/react-query": "^5.75.5",
|
||||||
"@tanstack/react-query-devtools": "^5.75.5",
|
"@tanstack/react-query-devtools": "^5.75.5",
|
||||||
"@tanstack/react-table": "^8.21.2",
|
"@tanstack/react-table": "^8.21.2",
|
||||||
@@ -39,11 +32,11 @@
|
|||||||
"next": "15.5.18",
|
"next": "15.5.18",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"tus-js-client": "^4.3.1",
|
"tus-js-client": "^4.3.1",
|
||||||
"uuid": "^14.0.1",
|
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@hookform/devtools": "^4.4.0",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"eslint": "^9.0.0",
|
"eslint": "^9.0.0",
|
||||||
"eslint-config-next": "15.3.8",
|
"eslint-config-next": "15.3.8",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 483 KiB |
@@ -0,0 +1,63 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 538 755">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<!-- Generator: Adobe Illustrator 28.7.9, SVG Export Plug-In . SVG Version: 1.2.0 Build 218) -->
|
||||||
|
<g>
|
||||||
|
<g id="Layer_1">
|
||||||
|
<g id="han" data-label-shift="dx,dy">
|
||||||
|
<path class="cls-1" d="M159.2,197l-5,5.8,5.1,3.7,13.2,9.7-7.3,3.5-8.2-1.2-9.4,1.4,1.4,4.9-3.5,7.3,3.8,2.6-.9,4.7,8.9-2.3,4.2-3.7,5.2,4.2-6.1,2.3.9,12,3,7-.9,3.3-.5,1.6,5.4,6.8,5.2,13.4h-3.6c0-.1,6.4,3.8,6.4,3.8l4.8,6h1.2l3.6-5.4,3.6-.6,13.8,5.9,19.7-1.1,4.6-6.5,3-5.2,8.2-1.7,11.7-17.8,7.1,1.9,4.9,10,13.2,9.6,3.8,13-1.4,12.2-.5,9.5,1.6,1.1-.6,1.3v1.1c.1,0-2.2,2.4-2.2,2.4l-17.5,7.4,7.6,19.2,16.3,1.1,8.4,10.8,12.6.6,4.2-5.2-4.6-7.9-9.2-6.2,12.1-7.1,6.4,5.2,1.3-5.6-6.6-6.5,17.5-5.8,4.8,4.4,4.8-2.1-4.7-6.5,4.5-8.8,6.3-.4,4.7-3,8,3,24.1,4.5,11.5-7.8-2.8-6.4,6.5-11.7,10.5-5.5,7.6-7.9,10.2-2.3,13.4,4.6,4.4-8,12.4,8.2,3.4-3.1.2-15.2.4-11.3,13.4-3.2,4.6-.4-1.9,9.9,9.8,4.6,1.6,12.8,12.2,9.2,9.8-8.4,15-8.9-3.7-19-21.8-40.6-5.7-10.6,1.2-6.8-4.9-6.1-4-4.9,1.2-5.2-26.3-33.1-5.8-12.1-7.1-14.9-11.9-20.9-2.9-9.4-2.1-6.7-1.8-5.2-8.7-25.5-8.6-14.4-.9-11-5.2-11-3-6.3-6,6.5-5.2,22.8-9.8,11-5.5,2.8-7.4,3.7-17.1.5-12.7-1.6-8.2,2.3-5.2-3.4-3.4-2.2-18.8-2.3-21.8,4.5-19-3.7-3.5,4.7-11.2-7.3-5.2,4.2-6.1-.7-11.5,3.5-14.1,10.8-3.7,10.1-12.9,9.8-4,7.3-8.5,6.8-2.3,1.9-9.1,4.5-4,18.3-9.2,11.5-14.3,1.2-2.1,30,5.6,1.4,3.3,9.2-1.4,2.7.9,2.6,2.9-.7,1.8,3.2-4.5,2.2,1.7,8.6,3.8,6.9,8.9,3.4Z"/>
|
||||||
|
</g>
|
||||||
|
<g id="yeongsan" data-label-shift="dx,dy">
|
||||||
|
<path class="cls-1" d="M57.4,578.4s.2,1.4.2,2.3.7,1.6.7,1.9.5,1.4.7,2.3c.2.9,1.2,1.2,1.2,1.2h0s.5,1.4.5,1.4c0,0-.5.7-.9,1.4-.5.7,1.6.9,1.6.9l2.8-.7s1.4-.7,1.4-1.2,0,0-.2-.7c-.2-.7-.5-.7-.9-1.2-.5-.5.5-.2.5-.2l.5.2,2.6-.5s.9-.5,1.4-.7c.5-.2-.2-.5-.2-.9s-.2-1.2-.2-1.2l.2-.9v-1.2c0-.5.7-.7.9-1.2.2-.5.5-.7.9-1.2.5-.5,0-.7,0-1.6s-.5-.5-.5-.9-.5,0-.5,0l-.9.5-.9.7s-.2.9-.5,1.2c-.2.2-1.4.2-1.4.2,0,0-.2-.5-1.2-.9-.9-.5,0,0-.5,0s-1.2-.2-1.2-.2c0,0-1.2-.7-1.6-.9-.5-.2-.5-.9-.5-.9h-1.2s-1.9,1.4-2.8,1.9c-.9.5,0,1.2,0,1.2Z"/>
|
||||||
|
<path class="cls-1" d="M45.2,585.7c.2.2,0,.2.7.9.7.7.5,1.6.9,1.6s.2-.5,1.2,0c.9.5,1.4.5,1.9.7.5.2,1.6.9,1.9.9s1.4.7,1.4.9-.2.2,0,.5c.2.2,0,.5.5,1.4.5.9,1.9,2.6,1.9,2.6,0,0-.7.7.5.5,1.2-.2,1.9-.2,1.9-.9s.2-1.2.2-1.4-.2-.2-.5-1.2c-.2-.9-.7-1.6-.7-1.6,0,0,1.2-.2,0-1.2-1.2-.9-.2-1.2-1.2-.9-.9.2-1.9.2-1.9.2-.2-1.2-.2-1.9-.2-1.9l-.5-.5.7-1.4h0c.5,0,.9-.2,1.2-.2s.9-.7.9-.7c0,0,.2-.2,0-.7-.2-.5-.2-.2-.9-.7s-1.6-.7-1.6-.7l-2.1-1.2-1.4-.9c-.7-.2-2.6.9-2.6.9,0,0,.9-.5,0,.7-.9,1.2-2.3,1.9-2.3,1.9,0,0-.2.5,0,.9.2.5,0,1.2.2,1.4Z"/>
|
||||||
|
<path class="cls-1" d="M28.6,585.5s0,.7,1.2.7h2.1c.5,0,.9.7.9.7v1.9l1.2.7s1.4,0,1.9-.5c.5-.5.5-.7.5-.7l-.5-1.4,2.3-.7s.5-.5.7-1.2c.2-.7-.2-1.2,0-2.3.2-1.2,0-2.8,0-2.8,0,0,.9-1.4.9-2.1s1.9-2.6,1.9-2.8h0v-.7h-1.4c-.2,0-1.7.5-2.1.7-.5.2,0,0-.9,1.2s-.2,1.6-.2,1.6l-1.9.5-1.4.7-.9.2s-1.2-.2-2.1-.2-.5.9-.5.9c0,0-1.2,1.4-1.9,1.6-.7.2-.7.7-.7.7,0,0,.9,2.3.9,2.8v.5Z"/>
|
||||||
|
<path class="cls-1" d="M41.7,591.4c-.5.2-.7-1.2-.5,0,.2,1.2,1.2,2.1,1.2,2.1l1.4.9s.9-.2,1.4,0c.5.2.9,0,.9,0l1.9.7v.2s.7-.2.5-1.6c-.2-1.4,0-1.4,0-1.4l-.7-.9-2.6-1.4h0s-2.8-.7-3-.5c-.2.2-.5-.2-.5.2s.5,1.4,0,1.6Z"/>
|
||||||
|
<path class="cls-1" d="M34.2,597.7s0,0,0,0c0,0,0,0,0,0Z"/>
|
||||||
|
<path class="cls-1" d="M45,598.9l-.7-.7-.5-.9s.2-1.4-.2-1.4-1.9-.5-1.9-.5l-1.4-.7h0s-1.4,0-1.4,0c0,0-.9,0-1.2.7-.2.7-.9.5-.9.9s-2.1,1.2-2.5,1.4c0,0,.2.2.4.5.5.7.5,1.4,1.2,1.4s2.1.2,2.3,0c.2-.2.9-.7,1.2-.5.2.2.5.5.5.9s-.5,1.2-.5,1.6.5,0,0,.5c-.5.5.2,1.4.5,1.6.2.2-.7,2.1-.7,2.1,0,0-.5.5-.5.7s1.4.7,1.6,0c.2-.7.7-1.2.9-1.4.2-.2.9-.5.9-.5,0,0-.5.2.7.2s2.1.2,2.1-.5v-1.9s-.2-1.2,0-1.4c.2-.2,0-2.3,0-2.3Z"/>
|
||||||
|
<path class="cls-1" d="M18.5,619.7c.7,0,1.9-.9,2.3,0,.5.9,1.9,2.6,1.9,2.6,0,0,.2-.2,1.4,0,1.2.2,1.2.5,2.1,0,.9-.5,1.4-1.4,1.4-1.4l1.2-1.2,3.3-1.4s-.2-.7-.7-2.1c-.5-1.4-.7-2.1-.7-2.1,0,0,1.2.2.5-.9-.7-1.2-.9-1.9-.9-1.9l-.5-1.6h0s-.7-.9-.7-.9l-2.1.2s-.2,1.2,0,1.9c.2.7.2.9,0,1.4-.2.5-.7-.5-1.2,0-.5.5-2.3.2-2.3.2h-1.2l-.9,1.4-1.2.9-2.3.9-2.1,1.4s-.5,0,0,.9c.5.9,2.1,1.6,2.8,1.6Z"/>
|
||||||
|
<path class="cls-1" d="M33.1,624h-.9l-1.4-.7-2.1.7s-.9,1.2-.9,1.6.7,1.9,1.4,2.3c.7.5,1.6.7,1.9,1.6.2.9.2,1.9.2,1.9,0,0,1.9.7,2.3-.7.5-1.4.2-2.3.7-2.6.5-.2,1.9-1.6,1.9-1.6,0,0-.5-.5.7-.5s1.6-.5,1.6-.7v-1.9l-.9-.9h.9l.7-.9s.2-.7,0-1.2c-.2-.5-.5-1.9-.5-1.9l-2.6.5h0l-1.4,1.4-.5.9s.2.9,0,1.2c-.2.2-1.2,1.4-1.2,1.4Z"/>
|
||||||
|
<path class="cls-1" d="M65.6,620.2l1.9-.7-2.1-2.6-3.5-1.9h0s-3.3.7-3.3.7l1.6,2.1.9,1.4s-.5.5-.9.9c-.5.5-1.6.7-1.6.7,0,0-.5.5-.5.9s-.9,0-2.1.7c-1.2.7-4.5,1.9-4.5,1.9h-.9c.9,0,1.6,2.6,2.3,2.6h3.1s-.2-.5.9-.7c1.2-.2,2.6-1.2,2.6-1.2,1.4.2,2.3-.7,3,.5.7,1.2,1.4.9,1.6,1.4.2.5,1.2,2.1,1.2,2.1l-.7,2.6s1.2.9,1.6.7c.5-.2,1.9-1.4,1.9-1.4l.9-1.9s-.5-1.4,0-1.9c.5-.5-1.6-1.4-1.6-1.4l-2.8-.7-1.9-2.6v-1.2l2.8-1.2Z"/>
|
||||||
|
<path class="cls-1" d="M64.2,635.5v-1.6s-3.3,1.4-3.3,1.6.9,1.6.9,1.6h1.6l.7-1.6Z"/>
|
||||||
|
<path class="cls-1" d="M59.8,639.2c0,1.2.5,2.8,1.2,3,.7.2,1.6,0,1.6,0v-3l-1.6-.9s-1.2-.2-1.2.9Z"/>
|
||||||
|
<path class="cls-1" d="M38.4,638.3c1.2,0,2.8-4.5,2.8-4.5v-1.4h0s-3.5,0-3.5,0l-2.1,1.4-3.5.7s-.2,1.9,1.4,1.9.7-.1.6-.2c0,0,.3,0,.6.2,1.6.5,3.3.7,3.3,1.2s-.7.7.5.7Z"/>
|
||||||
|
<path class="cls-1" d="M15.4,630.1h0s0,0,0,0h0Z"/>
|
||||||
|
<path class="cls-1" d="M7,642.7c.2-.5,1.2-.7,1.2-1.4s-1.6-2.6-.5-3.1c1.2-.5,2.6-.9,2.8-1.2.2-.2,1.2-.7,1.6-.5s.9.9.9.9l1.4-.5.5-.9s.2-.9.5-1.4c.2-.5.7-1.9.7-1.9v-1.2l-.7-1.6c-.3.2-2.6,2.1-2.8,2.3-.2.2-3,1.2-3.3,1.2s-3.1.2-3.1.2l-3.8.5-2.6,1.9,1.9,2.1s-.2,1.9,0,2.1c.2.2-.5,1.2.5,1.6.9.5,1.6.9,1.6.9,0,0,2.8.2,3-.2Z"/>
|
||||||
|
<path class="cls-1" d="M16.6,649.1c0-.7-1.2-1.6-1.2-1.6l-.7-.7-.9-.9s-.2-1.2-.2-1.4-.7-.9-.7-.9c0,0-2.3.2-2.3.5s-.7.7-.7.7c0,0-.2.7-1.6.7s-2.8-.5-2.6,0c.2.5-.2.9-.2,1.6s-.5,1.9-.5,1.9v1.2s.2,1.2.7,1.4c.5.2,1.2.5,1.4.7.2.2,1.4.9,1.2,1.4-.2.5,2.3.2,2.3.2l.5-.5c.2-.2,1.9-1.2,1.9-1.2,0,0,0-.9.5-.9s3.3-.5,3.3-.5v-1.6Z"/>
|
||||||
|
<polygon class="cls-1" points="12.9 643.4 12.9 643.4 12.9 643.4 12.9 643.4"/>
|
||||||
|
<path class="cls-1" d="M27.7,639.9c.5.5,1.4,1.6,2.1,1.9.7.2,1.2.5,1.4,1.2.2.7.2,1.4.5,2.1.2.7,1.9.7,1.9,1.2s.5-.2,1.4,0c.9.2.2-.2,1.6,1.2,1.4,1.4,2.3,2.1,2.6,2.6.2.5.9,0,.9,0l1.2-.9s-.2-1.6,0-1.6.2-2.1.2-2.3-.5-2.3-.5-2.3c0,0-1.2-1.4-1.4-1.4s-2.1-.7-2.1-.7l-1.2-1.2-.9-1.2-1.4-.5h0l-3.7-.7h-2.1l-1.2,1.6s.2.7.7,1.2Z"/>
|
||||||
|
<path class="cls-1" d="M32.6,663.4c.5.2,2.3.5,2.8.5s1.6-.2,1.9-.5c.2-.2.9-.2,1.6-.2s3.5.2,3.5.2c0,0,.5-.7.2-1.4-.2-.7-.9-1.4-.9-1.4l-.2-.7-.7-.5.7-1.2v-.9s-.7-.5-1.4-.5-1.4.2-1.4-.2-.9-1.9-.9-1.9l-1.4.7h0c-.9.5-1.9.7-2.1.9-.2.2-3.3.2-3.1.7.2.5.7,1.2.9,1.4.2.2.9,1.6.9,1.6,0,0-.5,1.6-.5,1.9s-.5,1.2,0,1.4Z"/>
|
||||||
|
<path class="cls-1" d="M21.3,670.4c0-.2,0-1.4.5-1.6.5-.2.7-.5.7-.7s-.7-1.2-.7-1.4-1.2-1.2-1.2-1.2l-.5-1.2-.2-1.2-.2-1.9h0s-.9-1.9-1.2-1.9-1.4-.2-1.4,0-.5.9-.5.9v.7c0,.2-.2.7,0,1.2.2.5-.2,1.4,0,1.6.2.2.2.7.5,1.4.2.7.7,1.2.7,1.4s-.2,1.2-.2,1.2c0,0-.5-.7-.5.5s-1.9,1.9-.5,2.1c1.4.2,2.8.5,3,.5s1.6-.2,1.6-.5Z"/>
|
||||||
|
<path class="cls-1" d="M27.4,673s-.5-.9,0-1.6c.3-.5.5-.9.7-1.2.2-.5.9-.5.9-.9s.5-2.3,0-2.8c-.5-.5-.9-1.6-1.4-2.3-.5-.7-.2-.9-1.2-1.6-.9-.7-1.9-1.2-1.9-1.2l-1.2.2h0s-.7,2.1-.7,2.3.2,2.3.7,2.6c.5.2,1.6,1.4,1.9,1.9.2.5.2,1.2.2,1.6s-.7,2.1-.9,2.1,0,0-1.2.5c-1.2.5-2.6.2-3,.9-.5.7-1.2,1.4-1.2,1.4v1.4c0,.2-.2.7.5.7s2.3-.2,2.8-.5c.5-.2,1.4-.7,1.4-.7l2.3-2.1,1.2-.7Z"/>
|
||||||
|
<path class="cls-1" d="M63.7,683.1c-.2-.5-1.4-2.1-1.4-2.1l-.5-.9s0-1.6-.9-1.9c-.9-.2-3-.2-3-.2l-1.6-2.3s-1.4-2.6-1.6-2.6-1.4,0-2.1-.2c-.7-.2-2.6-1.2-2.6-1.2l-.9.7c.2.7,1.6,2.8,1.6,2.8l1.2,2.3-.5,1.9s-1.9.5-2.3.5h-3s-.5-.7-.7.7c-.2,1.4-2.6,2.6-2.6,2.6h-3s-.9,1.6-.9,2.1-6.1,3.1-6.1,3.1l-1.9,2.6-1.9.5s-.5.7-.5.9-1.2,1.6-1.2,1.6h-.7s-2.3.5-2.3.9.5,1.6.5,2.1v1.6s0,1.6.2,1.6,1.4.7,1.4.7l.7,1.2s.5.2.5.5.7,1.2.7,1.2l1.2,1.2s-.5,1.6,0,2.1c.5.5.9.5,1.4.7.5.2,4.2.7,4.2.7,0,0,1.9.2,2.1.5.2.2,1.4.5,2.1,0,.7-.5,7-1.6,7-1.6,0,0-.7-.9,0-1.2.7-.2,1.9-.5,3-1.4,1.2-.9,3.7-1.2,3.7-1.2,0,0-.5-1.2.2-1.6.7-.5,2.6-2.1,2.8-2.1s2.1,1.4,2.1,1.4c0,0-.7.7.5.7s1.4-.5,1.9-2.1c.5-1.6,1.9-2.3,1.9-2.3l.5-.7s-.5-.5,0-1.6c.5-1.2.9-2.6.9-2.6v-3.5s-.5,0,.5-.5c.9-.5,2.1-2.3,1.4-3.1-.7-.7-1.6-1.9-1.9-2.3Z"/>
|
||||||
|
<polygon class="cls-1" points="49 672.3 49 672.3 49 672.3 49 672.3"/>
|
||||||
|
<path class="cls-1" d="M93.5,737.5c-2.3-1.2-4.2-2.3-4.2-2.3h-1.9l-1.2,3.5s1.4,0,1.2.9c-.2.9-1.6.5-1.2,1.6.5,1.2-.5.7,1.6,1.6,2.1.9,2.8,1.9,3.7.9.9-.9,1.9-2.6,1.9-2.6h0l.7-1.2s1.6-1.4-.7-2.6Z"/>
|
||||||
|
<path class="cls-1" d="M86.2,745l-2.8-1.9s-2.8,2.6-2.8,3.1-2.1,3.3-1.6,4,1.2,1.6,2.8,1.6,3.3-1.6,5.4-2.6c2.1-.9,2.8-2.3,3.7-1.9.9.5,2.1-.5,2.1-.5h0l-1.9-1.2-4.9-.7Z"/>
|
||||||
|
<path class="cls-1" d="M101.7,742h0l-3.5,1.2s-.9,1.2-.9,1.6.5,2.6.5,2.6c0,0-.5.5-.5,1.6v2.3s1.2,3.1,2.1,3.5c.9.5,2.6-.5,3.1-1.9.5-1.4-.9-5.6-.9-5.6,0,0-2.8-1.2-2.1-1.4.7-.2,2.8-.5,3.1-.5s.9-.7.9-.7l-.7-2.8h-.9Z"/>
|
||||||
|
<path class="cls-1" d="M115.1,720.4l-1.2-1.6-.5-1.6h0v-2.6l-2.1-3.3s-1.6-1.2-1.9-1.6c-.2-.5-3-2.3-3-2.1s-2.3,2.3-2.3,2.6-3.7,2.8-3.7,2.8c0,0,1.2,4,1.2,4.2s-1.9.2-.5,1.9c1.4,1.6,5.9,2.1,5.9,2.1,0,0,.2-1.4.5.5.2,1.9-.5,2.8-.5,3.1s1.2,1.2,2.6,1.2,3.3-.2,3.5,0c.2.2,3.5.5,3.5.5,0,0,.2,0,0-2.1-.2-2.1-1.4-3.8-1.4-3.8Z"/>
|
||||||
|
<path class="cls-1" d="M125.6,711.9c.5,0,1.4-1.9,1.4-1.9,0,0-.5-.7.9-.5,1.4.2.9,0,1.6.5.7.5,1.2.5,1.4.5s1.2-.7.5-1.4c-.7-.7-1.4-.9-1.9-1.6-.5-.7-.5-2.6-.5-2.6,0,0-.5-1.4-.7-1.4h-2.8l-.9.9h0c-.7,0-3.5-.7-3.5-.7l-.5,2.8-2.1,1.4s-.9.5-.5,1.2c.5.7.9,1.2.7,1.6-.2.5-1.6,0-1.4.7.2.7,1.6,1.9,1.6,1.9,0,0-.7,1.2-.7,1.6s.9,1.4,1.4,1.4,1.9-2.1,1.9-2.1c0,0,.5-.9.9-1.2.5-.2,2.1-1.2,2.1-1.2h.9Z"/>
|
||||||
|
<path class="cls-1" d="M141.5,712.4c0-.2-.9-.5-1.2-.7-.2-.2-1.6-.7-1.6-.7l-3-.9h-1.7s-.7,2.1-.7,2.3-.5,1.2,0,1.4c.5.2,2.1.7,2.1.7v1.2c0,.5-.7.7.5,1.2,1.2.5,4.2-.5,4.2-.5h2.6s.9-1.4.5-1.9c-.5-.5-1.6-1.9-1.6-2.1Z"/>
|
||||||
|
<path class="cls-1" d="M128.6,725.1s.2-.2.5,0c.2.2,1.4-.5,1.4-.5,0,0-.2-.5.5-.5s1.9.5,1.9.5c0,0,.7-.2.9-.7.2-.5-.2-.9,0-1.4.2-.5,0-1.6,0-1.6l-.7-1.6s-.9-.2-1.4-.2-1.2-.2-1.2-.2l-.5-.2-.9-.7h0s-2.3-.2-2.6.5c-.2.7-.7,1.6-.9,1.9-.2.2-1.9.9-1.9.9l1.4,1.4s.2.7,1.2,1.2c.9.5,2.3,1.4,2.3,1.4Z"/>
|
||||||
|
<path class="cls-1" d="M121.1,719.9c-.3-.3-.7-.5-1.2-.5h0c-.6,0-1.1.6-1.2,1.2,0,.4.2.8.5,1.2.1.2.3.5.7.7.5.3,1.4.6,1.6.2.2-.2,0-.5-.2-1.6,0-.7,0-.9-.2-1.2Z"/>
|
||||||
|
<path class="cls-1" d="M137.3,747.6c-.9-.5-1.9-.9-1.9-.9l-.5-1.2.9-1.2.2-2.3-4.2-.5-2.1.5h0s-1.2,2.1-1.4,2.6c-.2.5-2.1,1.9-2.1,2.1s0,.7-.2,1.4c-.2.7-.9,1.9,0,2.3.9.5,2.1.7,2.1.7,0,0,2.6-1.4,2.8-.7.2.7.2,1.2,1.2,1.9.9.7,2.1.9,2.3.9s.2-1.2.9-1.4c.7-.2,2.6-1.9,2.6-1.9,0,0,.2-1.9-.7-2.3Z"/>
|
||||||
|
<path class="cls-1" d="M267.1,679.6s.5-.5,0-1.6c-.5-1.2-.7-2.1-.7-2.1l-.7-1.2-.5-1.4s-.5-.5-.2-1.2c.2-.7,0-2.6,0-2.6l-.2-.7.7-.9h0c.2-.2.5-2.1.5-2.1l-3.3-2.3s-1.4.9-1.2,1.9c.2.9.9,2.1.9,2.6s.2,1.6,0,2.1c-.2.5-.9,1.2-.9,1.4s-.7,1.4-.9,1.9c-.2.5-2.8,1.9-2.8,1.9,0,0-1.9,1.9-1.9,2.1s-1.2.2-.7,1.6c.5,1.4.5,3.3.5,3.3,0,0-1.2.9-.5,1.6.7.7,1.4,1.6,2.8,2.1,1.4.5,4.2,1.6,4.2,1.6l3.1.5s2.3-.9,1.9-1.4c-.5-.5-1.6-1.4-1.6-1.4l-.5-1.2s0-1.2.5-1.9,1.6-2.6,1.6-2.6Z"/>
|
||||||
|
<polygon class="cls-1" points="279.4 600.4 279.4 586.1 272 579.9 262.7 571.1 261.3 561.5 254.5 559.9 251.7 557.3 244.7 562.9 243.5 566.9 235.1 563.3 234.4 553 235.1 535.7 240.9 533.6 245.9 532.1 247.1 523.8 256.9 510.3 248.3 507 239.9 515.4 232.7 509.4 238.7 493.8 231.6 486.6 229.2 475.8 217.2 472.2 206.4 484.2 205.2 493.8 195.7 486.6 186.1 495 186.1 502.2 176.5 510.6 159.8 515.4 157.4 523.8 140.6 521.4 127.4 517.8 124.5 512.7 119.4 508.3 115.3 512.5 110.2 512.4 96.4 517.8 89.3 530 84.4 535 85.6 538.3 88.3 540.1 87.2 541.1 82.3 539.9 76.8 551.3 72.3 553.6 67.3 564.8 76.6 566.5 73.6 571.4 75.3 574.4 80.6 583.9 85.4 585.9 85.4 589.7 80 596 78.4 589.4 69.9 588.5 68.8 590 71.3 593.4 75.4 595.9 74.3 598.4 69.7 600 68.7 603.3 63 605.4 61.9 611.5 67.8 615.5 70.4 613.1 73.2 605.9 74.3 613.6 75.3 626.5 69.7 634.5 69.7 639.2 68.1 643.4 67.8 644.3 80.4 650.4 92.7 661.4 89.3 661.9 86.5 659.8 85.6 664 79 654.2 69.4 650 67.8 656.3 70.6 661.9 74.3 663.3 77.1 670.4 72.7 669.9 66.1 661.5 62.9 648.1 58.5 642.1 51.9 654.2 55.1 668.3 59.6 674.6 61.9 673.9 65.8 676.6 67.1 680.5 69.9 680.2 72.7 677.9 79 686.6 77.7 698.2 82.8 699.7 79.7 706.7 76.6 709.9 77.4 712.1 82.5 712.8 82.7 721.2 93.1 719.9 97.3 713.3 97.1 709.8 112.1 701.6 110.7 695.9 117.2 694.3 120.8 688.2 120.2 673.4 122.8 671.1 124.7 675.1 124.5 684.5 122.4 694.3 125.7 699.9 130.4 702.1 134.8 704.2 136.9 700.4 143.5 702.3 145.8 697.6 149.1 698.3 148.3 688.6 152.1 687 152.9 684.2 151.7 682 155 673 162 671.1 167.4 666.2 174.9 662.4 179.6 664 183.8 662.6 187.1 655.8 192.4 654.7 194.6 657.2 202.1 657.2 202.7 663.8 200.6 669.2 196 669.8 194.8 663.8 189.6 664 187.5 671.6 189.1 675.6 185.9 681.9 181.9 681.2 183.1 676 181.4 674.3 176 692.2 189.2 694.5 191.7 701.4 195.5 706.3 203.9 704.4 207.4 697.8 213.3 694.1 214.2 689.9 209.3 687.1 210 684 218 684.7 221 686.6 224.2 683.9 222.7 674.6 209.2 661.4 209.5 657 212.8 653.7 210.5 650.9 215.4 646.2 211.9 643.7 213.5 642.5 216.5 642.5 222 642.5 227.6 640.4 228.1 635.9 230.9 638 231.2 641.7 231.8 647.4 236.2 650.7 238.4 657 234.6 663.8 232.6 672.3 234.9 676.9 238.7 676.9 243.5 679.8 244.5 675.1 243.3 671.1 245.9 662.9 249.9 659.4 255 664.1 261.6 661 262.3 655.8 265.1 651.9 267 645.5 264.6 642 261.1 642.5 257.9 642 248 646 246.3 642 239.9 634.6 244.2 626.8 246.3 631 251.6 632.8 262.5 623.5 278.8 628.6 285.4 617.2 285.4 607.6 279.4 600.4"/>
|
||||||
|
</g>
|
||||||
|
<g id="nakdong" data-label-shift="dx,dy">
|
||||||
|
<path class="cls-1" d="M405.4,649.3l-2.1-.2-1.6,1.2-.9,1.6-1.2.2-.9-.5v-1.2l.2-.9,1.4-.9.9-.7v-.9l.9-.9.7-1.2-.2-1.6-.2-1.4.5-3v-1.9l-1.6-1.6-.9-.9.2-1.9.7-2.8h0c0,0,1.6-.5,1.6-.5,0,0,1.2-.9,1.2-1.2s.2-.7.2-.7l-.2-.9-1.4-.9-1.9-.7-1.6.2-1.2.5-.9,1.2-.7,1.2-.2,1.2.5,1.9v.9l-.5.7-.7.2-1.2.2-.9.5-.5.5-.2,1.2-.2.5h-1.4l-1.4.5s-.9.2-1.2.2-.7.7-.7.7l-1.2.5h-1.4c-.2,0-.7.2-.7.2l-.5.5v.5l.2.7s.7.7.9.7,0,.7,0,.7l.2.9v.7l.2.9.5.5.7.5s.5.5.7.5.2.7.2.7c0,0,.5.7.5.9v2.1h-.2s-.7-.2-.9-.2-.5-.5-.5-.5l-.9-.7h-.9l-.9-.5-.5-.7-.7-1.2-.7-.5-.5-.2-.5.2-.7.7-.7.2h-.9l-1.4-.9-1.4-.9-1.4-.7h-1.2l-.5.2-.7.9-.5.7-1.2.7s-.2,0-.5.2c-.2.2-.7.5-.7.5l-.5.7-.7,1.2-.5,1.2-.9.9v3.3l1.2,1.4s.7,1.4.7,1.6.2,1.4.2,1.4l.7,1.2.7,1.6,1.6.2,1.4-.9.7-1.2.7-.7h2.6l1.4-1.2.9-1.6.7-.9.7-.5.5.5.5.7v2.6s.5.5.7.5,0,.7,0,.7l-.7,1.2-1.2,1.9-.5.7-.2.5-.2.5-.9.5-1.2.2-1.4.2-.7.5-.5.5-.2.7,1.4.5.5.5v.7l.5.5.7.2h1.2l2.1.2.7.5.2.7-.2.5-.7.5s-.9.7-1.2.7-.5.9-.5.9l-.2.7.2.9v.7l.9.2.9.7,1.2.2v.9l-.2.2-.9.5-1.4.5-.7.2-.5.7v.9l.5.7s.5.5.7.5,1.4.2,1.4.2c0,0,1.4.2,1.6.2s.9-.5.9-.5l1.2-.5,1.2-.7.5-.7v-.7l.2-1.2s.5-.9.5-1.2.9-.5.9-.5l1.4-.2.2-.7-.9-.9-.7-1.4.5-1.6v-1.2l.7-.7,1.6-.2,1.6-.2.7-.9.2-1.2v-2.1l.7-.7,1.9.2,1.6-.2.5,2.1,1.2,1.4,2.1.9,1.6-.7.2-1.2-.2-1.2-1.6-1.2-.7-1.4.2-1.2-1.2-.7-.9-.9.5-1.4h1.9c.2,0,.7-.5.7-.5l.7-.9v-1.6h.9c.2,0,.7-.5.7-.5l.2-1.4s.2-.5.7-.7c.5-.2,0-1.4,0-1.4v-1.4Z"/>
|
||||||
|
<path class="cls-1" d="M330.3,650.8v-1.2l-.9-.5h0s-4.2-1.2-4.2-.9-2.8,0-2.8,0l-1.4.9s-.2.7-.2.9.2,1.2.2,1.2l.5.2.9.7,2.8.5h1.9l.9-.7.9-.7.9-.2.5-.2Z"/>
|
||||||
|
<polygon class="cls-1" points="333.8 654.8 332.6 654.1 332.2 652.9 331.7 652.7 331 652.9 329.8 653.9 329.6 654.8 329.1 656 329.1 657.6 329.4 659 329.8 659.7 330.8 659.7 331.9 659.5 332.4 659 333.8 659 335 658.8 335.2 657.8 335.2 656.9 334.7 655.7 333.8 654.8"/>
|
||||||
|
<polygon class="cls-1" points="308 649.2 307.6 648.4 307.6 647.5 308 646.3 308.7 645.2 308.7 644.2 308.3 644 307.3 643.5 306.4 642.8 305.5 641.9 304.7 641.2 303.1 641.4 302.2 642.8 302.2 644.7 301.2 645.2 300.8 644.2 300.5 642.6 301 641.4 301.7 639.8 302.4 638.4 303.6 637.2 303.6 636.5 303.3 635.8 302.6 635.8 301 636 299.8 637 298.4 638.1 298.2 639.5 297.3 640 295.6 640.9 294.7 642.4 293.5 643.5 293.3 644.9 293.3 645.9 293.5 647 294.2 648.2 295.4 648.4 297 649.2 299.4 649.2 301.2 648.7 303.1 649.2 304.1 649.9 306.2 651 307.8 652 308.3 651.7 309.4 651.7 309.7 651 309.2 650.3 308 649.2"/>
|
||||||
|
<path class="cls-1" d="M305.2,653.4l-1.2-.5-.9-.7-.7-.2h-2.1l-1.6-.2-.9-.7h-1.6l-1.2,1.2-1.6,1.2-.9.7v.7l-1.4-.2-.5-1.2-.9-1.4-.2-.7-.5-.9-1.2-.5-1.2-.5-.5-.5s-.2-.5-.2-.7-.2-1.2-.2-1.2v-.5c0-.2-.7-.7-.7-.7l-.2-.7-.5-1.2-.2-1.4.7-.9.2-.9,1.6-.2.7-.5.5-.2.2-.7.2-1.2.5-.5.7-.9v-.7l-.2-1.2-.5-1.4.2-1.4-2.1-1.4h-3.1l-1.9.2-1.4,1.2-.9,1.4-.5,1.2-.5,1.4v.7l-1.6.5-.9.5-.7.9s.2.5,0,.7c-.2.2-1.2.9-1.2.9v1.4l-.5,1.2-.7.5-.9.5-.2.9v1.4l.2,1.2-.2,1.4.5,1.6.5,1.9,1.2,1.9h0v1.6l.2,1.4.7.9.9.7.2.7-.2.7-.9.9-.9.7-.5.7-.2.7,1.2.7.7.5,1.4.7v.7l.2,1.2.5.9.9.5.9-.2h1.9l1.2-.2.9-.5.9-.9v-1.4l-.5-.9-.2-1.4.2-.7.7-.5,1.9-.2,1.2-.9h2.3l.5.9v.7s-.5.7-.5.9-.2,1.2-.2,1.2l-.5,1.4v.5l-.2.9.2.5.2.5.2,1.2v1.2l.5.9,2.1.2,2.3.7,1.6-.2,1.2.2.9-.7s.5.5.7.5.5.9.5.9l1.6.7,1.6.2h.7l.2-1.2-.2-1.4-.7-.7-.2-.9v-1.2l.5-.5.9-.9.7-.2.5-.5v-2.3s-.5-2.1-.5-2.3v-.9l.2-1.2.5-1.4s-.2-.9,0-.9.7-.7.7-.7l.7-.5.2-.9-.2-.9-1.2-.5Z"/>
|
||||||
|
<polygon class="cls-1" points="533.7 451.9 529.9 457.3 516.8 465.7 509.4 457.5 516.6 447.7 513.8 417.3 513.1 408.8 521.3 392.4 522.9 355.1 523.6 353.3 531.1 333.9 526.2 315.4 526.2 282.1 519.8 263.8 504.8 272.7 495 281.2 482.8 272 481.1 259.2 471.4 254.5 473.3 244.6 468.7 245 455.3 248.2 454.9 259.5 454.7 274.8 451.4 277.9 438.9 269.7 434.5 277.6 421.1 273 410.9 275.3 403.3 283.3 392.8 288.8 386.2 300.5 389 306.9 377.6 314.7 353.5 310.3 345.4 307.2 340.7 310.3 334.5 310.7 330 319.5 334.6 326 329.8 328.1 325 323.7 307.5 329.5 314.2 336 312.9 341.5 306.5 336.4 294.4 343.5 303.6 349.6 308.1 357.5 304 362.7 314.1 371.6 316.5 383.6 314.1 388.4 326.1 392 323.3 402.6 325.2 414.4 315.5 413.8 315 423.6 307.5 439.1 298 443 298.5 447.6 301.2 454.4 296.8 462.5 286.2 472.3 276.7 473.4 269.4 480.7 267.1 483.1 264.5 485.8 256.9 505.2 256.9 510.3 247.1 523.8 245.9 532.1 240.9 533.6 235.1 535.7 234.4 553 235.1 563.3 243.5 566.9 244.7 562.9 251.7 557.3 254.5 559.9 261.3 561.5 262.7 571.1 272 579.9 279.4 586.1 279.4 600.4 285.4 607.6 285.4 617.2 278.8 628.6 282.6 628.4 288.8 622.6 289.3 620.3 291.9 621.5 292.3 624.3 296.3 623 298.7 624.3 301.6 622.6 298.9 615.9 301.2 614.9 303.4 615.5 304.7 611.2 309.9 609.7 307.1 614.2 306.9 625.2 305.5 632.3 314.8 637.2 317 641.5 318.3 642.1 322.4 640.3 325.1 641.7 326.8 641.4 329.4 634.9 335.5 635.5 338.5 640.5 342.8 638.4 346.2 633 347.4 636.3 348.6 639.8 343.1 642.3 344.4 645.9 349.4 647.4 351.4 645.9 353.5 647.7 353.3 655.5 347.7 654.1 346.2 655.7 351 659.7 353.7 666.6 356.3 667.8 358.7 665.9 359.9 659.7 358.5 655.6 361.6 654.2 365.6 646.5 363.1 643.5 360.7 647.8 358.5 646.8 361.3 632.3 361.1 630.7 361 629.5 368.3 628 371.4 624.3 365.9 619.1 357.2 620.8 355.4 623.6 351.9 624.1 354.8 618.5 361.5 616.3 367.3 614.4 366.1 610.6 379 609.8 381.7 611.6 383.5 619.4 389.9 616.8 383.5 596.4 387.2 591.5 388.9 592.4 387.7 599.2 390.1 604.4 394.7 605 398 608.8 399.5 604.8 401.5 606.5 400.5 612.2 406.2 614.7 414 613.3 414.9 615.6 420.1 616.2 424.6 617.3 428.8 614.1 432.5 616.6 438.5 610.1 436.6 622.3 438 626.7 441.3 625.8 442.5 620.6 445.7 622.8 453.5 612.9 457 616.4 461.6 616.6 461.7 613.8 459.8 610.5 463.1 606.6 469.9 608.2 480.9 594.4 480.7 591.3 485.5 581.8 491 579.8 494.6 576.1 498.5 573.7 497.1 571.8 499.6 567.6 499.9 561.3 498 558.2 504.1 554 504 546.1 505.5 548.8 506.9 554.5 511.6 551.2 514.6 544.4 514.4 537.4 515.8 532.7 513.9 528 514 525.4 524.7 493.4 527.1 486.2 527.8 477.8 535.1 466.5 538 455.6 533.7 451.9"/>
|
||||||
|
</g>
|
||||||
|
<g id="geum" data-label-shift="dx,dy">
|
||||||
|
<path class="cls-1" d="M298,443l9.5-3.9,7.5-15.6.5-9.8,9.7.6-1.9-11.8,2.8-10.6-12-3.6,2.4-4.8-2.4-12-10.1-8.9-12.6-.6-8.4-10.8-16.3-1.1-7.6-19.2,17.5-7.4,2.3-2.4v-1.1c-.1,0,.4-1.3.4-1.3l-1.6-1.1.5-9.5,1.4-12.2-3.8-13-13.2-9.6-4.9-10-7.1-1.9-11.7,17.8-8.2,1.7-3,5.2-4.6,6.5-19.7,1.1-13.8-5.9-3.6.6-3.6,5.4h-1.2l-4.8-6-6.4-3.9-5-.2-9.1-18.5-5.4,3-2.4-5.7-11.4-2.8-11.7-9.2h-3.9c0,.1-3.4,4.8-3.4,4.8l1,6.2,2.1,5.4-1.2.8-4.4-7.5-6.7-3.2-6.3-.3-5.2,3.3,5.9,3.4-3.6,2.2,5.3,4.4,2.5.8.4,3-6.5,6.1-8.7,11.2-4-1.9,6.4-13.5-1.6-1.9,1.1-7.9-2.1-3-4.7,14.5-3,1.8-3.8-5.7-4.4,2.7.5,8.4-1.5,1.7-3.3-.8-4.2,3.2-5.1,13.8.5,1.5,3.9-.2.2-6.5,5.6,2.5,2.8,5.3-7.4,2.4v3c0,0,5.9,1.2,5.9,1.2l4.3-1.5,2.1-3.3,4-.3-1.4,6,4.1,3.7-2.4,9.4,4.3,6.1,1.1,27,11.6,2.1-6.6-26.7,2.3-4.1-6.3-4.9,1.7-1.9,1-6.9,2-1.5,2.2,9,4.4,3.2,3.1-1.9,3.1-6.1,1.8,1.5-1.9,8.6,2,14.4,1.9,13.7-2.8,6.5,8.2,7.8v2.1l-6.9,2.9,2.9,21.8-1.6,1.9-5.9,6.7,1.3,2.7,3.3-2.7,8.2,2.8,6.2,7.7-1.2,2.8,5.1,13.1,5.6,3.1-19.1.5-1.8,3.3,6.4,3.1-.6,10.7,7.3,2.8,3-3.3,8.9,1.9,9.4-3.4.3,1.4.2.9-3.1,5.4-5.1-.9-9.4,3.5,6.8,6.4,4.3,1.4-.2,5.6-20.6-5.8-6.4,13.7-18.1,10.4-1.9,6.6,5.9,4.7h19.9s3.8,4.2,3.8,4.2l-.5,3.4,3,5.1,13.2,3.6,16.8,2.4,2.4-8.4,16.8-4.8,9.6-8.4v-7.2l9.6-8.4,9.6,7.2,1.2-9.6,10.8-12,12,3.6,2.4,10.8,7.2,7.2-6,15.6,7.2,6,8.4-8.4,8.6,3.3v-5.1s7.6-19.4,7.6-19.4l12.2-12.4,9.6-1.1,10.5-9.8,4.5-8.1-2.7-6.8-.6-4.6Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
@@ -0,0 +1,62 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright 2026 Hancom Inc. All rights reserved.
|
||||||
|
~
|
||||||
|
~ https://www.hancom.com/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title>Hwp SDK Wasm Sample</title>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
data-type="css/app"
|
||||||
|
data-framework="aori|lab"
|
||||||
|
src="js/loadVersion.js"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
data-type="css/app"
|
||||||
|
data-framework="aori|lab"
|
||||||
|
src="js/loadHead.js"
|
||||||
|
></script>
|
||||||
|
<script data-main="js/main" data-bundle="dist/main_bundle" src="js/loadMain.js"></script>
|
||||||
|
<style>
|
||||||
|
#hcwoViewScroll::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
#hcwoViewScroll::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
#hcwoViewScroll::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="status" style="display: none">
|
||||||
|
<div class="sppiner-content">
|
||||||
|
<div class="spinner" id="spinner"></div>
|
||||||
|
<div class="emscripten" id="statustext"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="status_downloading">
|
||||||
|
<div class="status-content">
|
||||||
|
<div class="emscriptenDownloading">
|
||||||
|
<span id="statusDownloadingText">구성 중 ... 0% </span>
|
||||||
|
<span id="statusDownloadingPercent" class="emscriptenDownloadingPercent"></span>
|
||||||
|
</div>
|
||||||
|
<div class="emscriptenDownloadingInfo"></div>
|
||||||
|
<progress id="download_progress" value="0" max="100"></progress>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<iframe id="download_iframe" style="width: 0px; height: 0px; border: 0; display: none"></iframe>
|
||||||
|
<script type="text/javascript" src="hwpsdk/loadHwpsdk.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ko" style="height: 100%">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title>웹한글 기안기 브리지</title>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="http://hwp.onasys.co.kr/webhwpctrl/js/hwpctrlapp/utils/util.js"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="http://hwp.onasys.co.kr/webhwpctrl/js/hwpctrlapp/hwpCtrlApp.js"
|
||||||
|
></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="http://hwp.onasys.co.kr/webhwpctrl/js/webhwpctrl.js"
|
||||||
|
></script>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#hwpctrl {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hwpctrl_frame {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="hwpctrl"></div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
(function () {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
var baseUrl = params.get("baseurl") || "http://hwp.onasys.co.kr/webhwpctrl";
|
||||||
|
if (baseUrl.charAt(baseUrl.length - 1) !== "/") {
|
||||||
|
baseUrl += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
var HwpCtrl = BuildWebHwpCtrl(
|
||||||
|
"hwpctrl",
|
||||||
|
baseUrl,
|
||||||
|
function () {
|
||||||
|
try {
|
||||||
|
window.parent.postMessage({ type: "hangulgiangi-ready" }, "*");
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 부모(HangulGiangi)가 contentWindow.HwpCtrl 로 접근
|
||||||
|
window.HwpCtrl = HwpCtrl;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
||||||
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT ?? "local",
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
debug: process.env.NEXT_PUBLIC_SENTRY_DEBUG === "true",
|
|
||||||
});
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
||||||
environment: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT ?? "local",
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
debug: process.env.NEXT_PUBLIC_SENTRY_DEBUG === "true",
|
|
||||||
});
|
|
||||||
@@ -6,10 +6,19 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
html,
|
@layer base {
|
||||||
body {
|
:root {
|
||||||
color: var(--foreground);
|
--secondary-background: rgba(0, 0, 0, 0.04);
|
||||||
background: var(--background);
|
}
|
||||||
font-family: "Noto Sans KR", sans-serif;
|
[data-theme="black"] {
|
||||||
font-size: 10px;
|
--secondary-background: rgba(255, 255, 255, 0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
font-family: "Noto Sans KR", sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ async function findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]> {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findUserSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]> {
|
||||||
|
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/user/sido`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
async function findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
|
async function findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
|
||||||
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/gugun`, {
|
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/gugun`, {
|
||||||
params: { sido },
|
params: { sido },
|
||||||
@@ -20,7 +25,16 @@ async function findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDt
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findUserGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
|
||||||
|
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/user/gugun`, {
|
||||||
|
params: { sido },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
export const cmCdAdstDstrService = {
|
export const cmCdAdstDstrService = {
|
||||||
findSidoCombo,
|
findSidoCombo,
|
||||||
|
findUserSidoCombo,
|
||||||
findGugunCombo,
|
findGugunCombo,
|
||||||
|
findUserGugunCombo,
|
||||||
} satisfies CmCdAdstDstrService;
|
} satisfies CmCdAdstDstrService;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
import { IUserInfo } from "@/shared/store";
|
||||||
|
|
||||||
import { cmCdAdstDstrService } from "./adstdstr-service";
|
import { cmCdAdstDstrService } from "./adstdstr-service";
|
||||||
|
|
||||||
@@ -13,6 +14,16 @@ export function useFindSidoCombo() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useFindUserSidoCombo(userInfo?: IUserInfo | null) {
|
||||||
|
const adstDstrCdByUser = userInfo?.laeSeCd?.substring(2, 4);
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...cmCdAdstDstrBaseKey, "findUserSidoCombo", adstDstrCdByUser ?? "all"],
|
||||||
|
queryFn: () => cmCdAdstDstrService.findUserSidoCombo(),
|
||||||
|
select: (data) =>
|
||||||
|
adstDstrCdByUser ? data.filter((item) => item.adstDstrCd === adstDstrCdByUser) : data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function useFindGugunCombo(sido: string) {
|
export function useFindGugunCombo(sido: string) {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [...cmCdAdstDstrBaseKey, "findGugunCombo", sido],
|
queryKey: [...cmCdAdstDstrBaseKey, "findGugunCombo", sido],
|
||||||
@@ -20,3 +31,11 @@ export function useFindGugunCombo(sido: string) {
|
|||||||
enabled: Boolean(sido),
|
enabled: Boolean(sido),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useFindUserGugunCombo(sido: string) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...cmCdAdstDstrBaseKey, "findUserGugunCombo", sido],
|
||||||
|
queryFn: () => cmCdAdstDstrService.findUserGugunCombo(sido),
|
||||||
|
enabled: Boolean(sido),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
export { cmCdAdstDstrService } from "./api/adstdstr-service";
|
export { cmCdAdstDstrService } from "./api/adstdstr-service";
|
||||||
export { useFindGugunCombo, useFindSidoCombo } from "./api/queries";
|
export {
|
||||||
|
useFindGugunCombo,
|
||||||
|
useFindSidoCombo,
|
||||||
|
useFindUserGugunCombo,
|
||||||
|
useFindUserSidoCombo,
|
||||||
|
} from "./api/queries";
|
||||||
export type {
|
export type {
|
||||||
CmCdAdstDstrComboResponseDto,
|
CmCdAdstDstrComboResponseDto,
|
||||||
CmCdAdstDstrService,
|
CmCdAdstDstrService,
|
||||||
|
|||||||
@@ -7,5 +7,7 @@ export interface CmCdAdstDstrComboResponseDto {
|
|||||||
/** Controller public 메서드명·시그니처와 동일 */
|
/** Controller public 메서드명·시그니처와 동일 */
|
||||||
export interface CmCdAdstDstrService {
|
export interface CmCdAdstDstrService {
|
||||||
findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
|
findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
|
||||||
|
findUserSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
|
||||||
findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
|
findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
|
||||||
|
findUserGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { client } from "@/shared/api";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
AiAutoPrcsLatestResponseDto,
|
||||||
|
AiAutoPrcsMngService,
|
||||||
|
} from "../model/ai-auto-prcs-mng-interface";
|
||||||
|
|
||||||
|
const BASE = ENDPOINT.AS_SERVICE.AI_AUTO_PRCS_MNG;
|
||||||
|
|
||||||
|
async function findLatestPerAutoSeByFnrsDocNo(
|
||||||
|
fnrsCnsltnDocNo: string,
|
||||||
|
atmtnSeComCd?: string,
|
||||||
|
): Promise<AiAutoPrcsLatestResponseDto[]> {
|
||||||
|
const response = await client.get<AiAutoPrcsLatestResponseDto[]>(
|
||||||
|
`${BASE}/fnrs/${fnrsCnsltnDocNo}/latest`,
|
||||||
|
{ params: atmtnSeComCd ? { atmtnSeComCd } : undefined },
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const aiAutoPrcsMngService = {
|
||||||
|
findLatestPerAutoSeByFnrsDocNo,
|
||||||
|
} satisfies AiAutoPrcsMngService;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import { aiAutoPrcsMngService } from "./ai-auto-prcs-mng-service";
|
||||||
|
|
||||||
|
export const aiAutoPrcsMngKeys = {
|
||||||
|
all: [ENDPOINT.AS_SERVICE.AI_AUTO_PRCS_MNG] as const,
|
||||||
|
latest: (fnrsCnsltnDocNo: string, atmtnSeComCd?: string) =>
|
||||||
|
[...aiAutoPrcsMngKeys.all, "latest", fnrsCnsltnDocNo, atmtnSeComCd ?? "all"] as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UseAiAutoPrcsLatestOptions {
|
||||||
|
enabled?: boolean;
|
||||||
|
atmtnSeComCd?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAiAutoPrcsLatest(
|
||||||
|
fnrsCnsltnDocNo: string | null | undefined,
|
||||||
|
options: UseAiAutoPrcsLatestOptions = {},
|
||||||
|
) {
|
||||||
|
const { enabled = true, atmtnSeComCd } = options;
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: aiAutoPrcsMngKeys.latest(fnrsCnsltnDocNo!, atmtnSeComCd),
|
||||||
|
queryFn: () =>
|
||||||
|
aiAutoPrcsMngService.findLatestPerAutoSeByFnrsDocNo(fnrsCnsltnDocNo!, atmtnSeComCd),
|
||||||
|
enabled: Boolean(fnrsCnsltnDocNo) && enabled,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export { aiAutoPrcsMngService } from "./api/ai-auto-prcs-mng-service";
|
||||||
|
export { aiAutoPrcsMngKeys, useAiAutoPrcsLatest } from "./api/queries";
|
||||||
|
export type {
|
||||||
|
AiAutoPrcsLatestResponseDto,
|
||||||
|
AiAutoPrcsMngService,
|
||||||
|
AiAutoSeComCd,
|
||||||
|
} from "./model/ai-auto-prcs-mng-interface";
|
||||||
|
export { AI_AUTO_SE } from "./model/ai-auto-prcs-mng-interface";
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/** 자동화구분 공통코드 (AIAUTOSE) */
|
||||||
|
export const AI_AUTO_SE = {
|
||||||
|
FILE_VRFC: "FILE_VRFC",
|
||||||
|
DRAFT_GEN: "DRAFT_GEN",
|
||||||
|
COST_VRFC: "COST_VRFC",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AiAutoSeComCd = (typeof AI_AUTO_SE)[keyof typeof AI_AUTO_SE];
|
||||||
|
|
||||||
|
/** 백엔드 AiAutoPrcsLatestResponseDto */
|
||||||
|
export interface AiAutoPrcsLatestResponseDto {
|
||||||
|
atmtnPrcsUuid?: string;
|
||||||
|
fnrsCnsltnDocNo?: string;
|
||||||
|
pwrkCnsltnDocNo?: string;
|
||||||
|
crtDmndUuid?: string;
|
||||||
|
atmtnSeComCd?: string;
|
||||||
|
atmtnSeComNm?: string;
|
||||||
|
bfrDocSttsComCd?: string;
|
||||||
|
bfrDocSttsComNm?: string;
|
||||||
|
aftrDocSttsComCd?: string;
|
||||||
|
aftrDocSttsComNm?: string;
|
||||||
|
atmtnPrcsSttsComCd?: string;
|
||||||
|
atmtnPrcsSttsComNm?: string;
|
||||||
|
dmndDt?: string;
|
||||||
|
dmndSysId?: string;
|
||||||
|
rplyDt?: string;
|
||||||
|
rplyRsltComCd?: string;
|
||||||
|
prcsErrCn?: string | null;
|
||||||
|
lastSttsInqDt?: string;
|
||||||
|
sttsInqRsltComCd?: string;
|
||||||
|
rtrsmCtpvNmtm?: number;
|
||||||
|
lastRtrsmDt?: string;
|
||||||
|
lastFrcSttsChgYn?: string;
|
||||||
|
isFailed?: boolean;
|
||||||
|
isRetryable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Controller public 메서드명·시그니처와 동일 */
|
||||||
|
export interface AiAutoPrcsMngService {
|
||||||
|
findLatestPerAutoSeByFnrsDocNo(
|
||||||
|
fnrsCnsltnDocNo: string,
|
||||||
|
atmtnSeComCd?: string,
|
||||||
|
): Promise<AiAutoPrcsLatestResponseDto[]>;
|
||||||
|
}
|
||||||
@@ -3,7 +3,13 @@ import { AxiosResponse } from "axios";
|
|||||||
import { client } from "@/shared/api";
|
import { client } from "@/shared/api";
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
import type { AttachService } from "../model/attach-interface";
|
import type { Attach, AttachService } from "../model/attach-interface";
|
||||||
|
|
||||||
|
async function findByAtflCd(atflCd: string): Promise<Attach[]> {
|
||||||
|
if (!atflCd) return [];
|
||||||
|
const response = await client.get<Attach[]>(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflCd}`);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
async function deleteAttachmentByAtflId(atflId: string): Promise<void> {
|
async function deleteAttachmentByAtflId(atflId: string): Promise<void> {
|
||||||
if (!atflId) return;
|
if (!atflId) return;
|
||||||
@@ -16,7 +22,15 @@ async function downloadAttachmentByAtflCd(atflCd: string): Promise<AxiosResponse
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function downloadAttachmentByAtflId(atflId: string): Promise<AxiosResponse> {
|
||||||
|
return await client.get<Blob>(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD_BY_ATFL_ID}/${atflId}`, {
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const attachService = {
|
export const attachService = {
|
||||||
|
findByAtflCd,
|
||||||
deleteAttachmentByAtflId,
|
deleteAttachmentByAtflId,
|
||||||
downloadAttachmentByAtflCd,
|
downloadAttachmentByAtflCd,
|
||||||
|
downloadAttachmentByAtflId,
|
||||||
} satisfies AttachService;
|
} satisfies AttachService;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
import type { AxiosResponse } from "axios";
|
||||||
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
@@ -8,6 +9,22 @@ import { attachService } from "./attach-service";
|
|||||||
|
|
||||||
const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const;
|
const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const;
|
||||||
const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const;
|
const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const;
|
||||||
|
const attachDownloadByAtflIdKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD_BY_ATFL_ID] as const;
|
||||||
|
|
||||||
|
function triggerBlobDownload(response: AxiosResponse) {
|
||||||
|
const filename = parseFilenameFromResponse(response);
|
||||||
|
const blob = new Blob([response.data]);
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
export function useDeleteAttachment() {
|
export function useDeleteAttachment() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@@ -21,19 +38,17 @@ export function useDownloadAttachmentByAtflCd() {
|
|||||||
mutationKey: [...attachDownloadBaseKey, "download"],
|
mutationKey: [...attachDownloadBaseKey, "download"],
|
||||||
mutationFn: async (atflCd: string) => {
|
mutationFn: async (atflCd: string) => {
|
||||||
const response = await attachService.downloadAttachmentByAtflCd(atflCd);
|
const response = await attachService.downloadAttachmentByAtflCd(atflCd);
|
||||||
const filename = parseFilenameFromResponse(response);
|
triggerBlobDownload(response);
|
||||||
|
},
|
||||||
const blob = new Blob([response.data]);
|
});
|
||||||
const url = window.URL.createObjectURL(blob);
|
}
|
||||||
const link = document.createElement("a");
|
|
||||||
|
export function useDownloadAttachmentByAtflId() {
|
||||||
link.href = url;
|
return useMutation({
|
||||||
link.download = filename;
|
mutationKey: [...attachDownloadByAtflIdKey, "download"],
|
||||||
document.body.appendChild(link);
|
mutationFn: async (atflId: string) => {
|
||||||
link.click();
|
const response = await attachService.downloadAttachmentByAtflId(atflId);
|
||||||
|
triggerBlobDownload(response);
|
||||||
window.URL.revokeObjectURL(url);
|
|
||||||
document.body.removeChild(link);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
export { attachService } from "./api/attach-service";
|
export { attachService } from "./api/attach-service";
|
||||||
export { useDeleteAttachment, useDownloadAttachmentByAtflCd } from "./api/queries";
|
export {
|
||||||
|
useDeleteAttachment,
|
||||||
|
useDownloadAttachmentByAtflCd,
|
||||||
|
useDownloadAttachmentByAtflId,
|
||||||
|
} from "./api/queries";
|
||||||
|
export { parseFilenameFromResponse } from "./lib/parse-filename-from-response";
|
||||||
export type { Attach, AttachService } from "./model/attach-interface";
|
export type { Attach, AttachService } from "./model/attach-interface";
|
||||||
|
export {
|
||||||
|
AttachmentDownloadLink,
|
||||||
|
type AttachmentDownloadLinkProps,
|
||||||
|
} from "./ui/attachment-download-link";
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export interface Attach {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AttachService {
|
export interface AttachService {
|
||||||
|
findByAtflCd: (atflCd: string) => Promise<Attach[]>;
|
||||||
deleteAttachmentByAtflId: (atflId: string) => Promise<void>;
|
deleteAttachmentByAtflId: (atflId: string) => Promise<void>;
|
||||||
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
|
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
|
||||||
|
downloadAttachmentByAtflId: (atflId: string) => Promise<AxiosResponse>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Paperclip } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "@common/components";
|
||||||
|
|
||||||
|
import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "../api/queries";
|
||||||
|
|
||||||
|
interface AttachmentDownloadLinkProps {
|
||||||
|
atflCd?: string;
|
||||||
|
atflId?: string;
|
||||||
|
fileName?: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AttachmentDownloadLink({
|
||||||
|
atflCd,
|
||||||
|
atflId,
|
||||||
|
fileName,
|
||||||
|
className,
|
||||||
|
}: AttachmentDownloadLinkProps) {
|
||||||
|
const downloadByAtflCd = useDownloadAttachmentByAtflCd();
|
||||||
|
const downloadByAtflId = useDownloadAttachmentByAtflId();
|
||||||
|
const displayName = fileName || "첨부파일";
|
||||||
|
const canDownload = Boolean(atflId || atflCd);
|
||||||
|
const isPending = atflId ? downloadByAtflId.isPending : downloadByAtflCd.isPending;
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
if (atflId) {
|
||||||
|
downloadByAtflId.mutate(atflId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (atflCd) downloadByAtflCd.mutate(atflCd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cn(
|
||||||
|
"inline-flex cursor-pointer items-start gap-1 text-left text-blue-600 hover:underline disabled:cursor-wait disabled:opacity-60",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
aria-label={`${displayName} 다운로드`}
|
||||||
|
disabled={isPending || !canDownload}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<Paperclip className="my-1 size-5 shrink-0" />
|
||||||
|
{displayName}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { AttachmentDownloadLink };
|
||||||
|
export type { AttachmentDownloadLinkProps };
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
|
import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
|
||||||
import { type AuthPayload, type AuthService, getRemainingMs } from "@common/auth";
|
import {
|
||||||
|
type AuthPayload,
|
||||||
|
type AuthService,
|
||||||
|
clearDeviceId,
|
||||||
|
getOrCreateDeviceId,
|
||||||
|
} from "@common/auth";
|
||||||
|
|
||||||
import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id";
|
import { clearRefreshTimer, setToken, tokenManager } from "../lib/token-manager";
|
||||||
import { clearRefreshTimer, setToken, setupRefreshTimer, tokenManager } from "../lib/token-manager";
|
|
||||||
|
|
||||||
async function login(payload: AuthPayload): Promise<void> {
|
async function login(payload: AuthPayload): Promise<void> {
|
||||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
|
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
|
||||||
@@ -26,10 +30,10 @@ async function login(payload: AuthPayload): Promise<void> {
|
|||||||
setToken(CLAIM_NAME, accessToken);
|
setToken(CLAIM_NAME, accessToken);
|
||||||
setToken(AUTH_USER_ID, tokenId);
|
setToken(AUTH_USER_ID, tokenId);
|
||||||
|
|
||||||
const remainingMs = getRemainingMs(accessToken);
|
// const remainingMs = getRemainingMs(accessToken);
|
||||||
if (remainingMs > 0) {
|
// if (remainingMs > 0) {
|
||||||
setupRefreshTimer(silentRefresh, remainingMs);
|
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function silentRefresh(): Promise<void> {
|
async function silentRefresh(): Promise<void> {
|
||||||
@@ -58,10 +62,10 @@ async function silentRefresh(): Promise<void> {
|
|||||||
setToken(CLAIM_NAME, accessToken);
|
setToken(CLAIM_NAME, accessToken);
|
||||||
setToken(AUTH_USER_ID, tokenId);
|
setToken(AUTH_USER_ID, tokenId);
|
||||||
|
|
||||||
const remainingMs = getRemainingMs(accessToken);
|
// const remainingMs = getRemainingMs(accessToken);
|
||||||
if (remainingMs > 0) {
|
// if (remainingMs > 0) {
|
||||||
setupRefreshTimer(silentRefresh, remainingMs);
|
// setupRefreshTimer(silentRefresh, remainingMs);
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[silentRefresh] Failed at ${new Date().toISOString()}`, error);
|
console.error(`[silentRefresh] Failed at ${new Date().toISOString()}`, error);
|
||||||
tokenManager.clearAuthToken();
|
tokenManager.clearAuthToken();
|
||||||
@@ -72,6 +76,7 @@ async function silentRefresh(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function logout(): Promise<void> {
|
async function logout(): Promise<void> {
|
||||||
|
await fetch("/api/auth/logout", { method: "POST" });
|
||||||
tokenManager.clearAuthToken();
|
tokenManager.clearAuthToken();
|
||||||
clearDeviceId();
|
clearDeviceId();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
|
|
||||||
const DEVICE_ID_KEY = "x-device-id";
|
|
||||||
|
|
||||||
export function getOrCreateDeviceId(): string {
|
|
||||||
if (typeof window === "undefined") return "";
|
|
||||||
const existing = localStorage.getItem(DEVICE_ID_KEY);
|
|
||||||
if (existing) return existing;
|
|
||||||
const id = uuidv4();
|
|
||||||
localStorage.setItem(DEVICE_ID_KEY, id);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearDeviceId(): void {
|
|
||||||
if (typeof window === "undefined") return;
|
|
||||||
localStorage.removeItem(DEVICE_ID_KEY);
|
|
||||||
}
|
|
||||||
@@ -8,6 +8,8 @@ import type {
|
|||||||
BnsListResponseWrapperDto,
|
BnsListResponseWrapperDto,
|
||||||
BnsSearchDto,
|
BnsSearchDto,
|
||||||
EmCodeListResponseDto,
|
EmCodeListResponseDto,
|
||||||
|
FacilityListResponseWrapperDto,
|
||||||
|
FacilitySearchDto,
|
||||||
} from "../model/bns-if-interface";
|
} from "../model/bns-if-interface";
|
||||||
|
|
||||||
const BASE = ENDPOINT.AS_SERVICE.BNS_IF;
|
const BASE = ENDPOINT.AS_SERVICE.BNS_IF;
|
||||||
@@ -40,8 +42,18 @@ async function getEmCodeList(
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getFacilityList(
|
||||||
|
params: FacilitySearchDto,
|
||||||
|
): Promise<FacilityListResponseWrapperDto> {
|
||||||
|
const response = await client.get<FacilityListResponseWrapperDto>(`${BASE}/facilityList`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
export const bnsIfService = {
|
export const bnsIfService = {
|
||||||
getBnsList,
|
getBnsList,
|
||||||
getBnsDetail,
|
getBnsDetail,
|
||||||
getEmCodeList,
|
getEmCodeList,
|
||||||
|
getFacilityList,
|
||||||
} satisfies BnsIfService;
|
} satisfies BnsIfService;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
|||||||
|
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
import type { BnsSearchDto } from "../model/bns-if-interface";
|
import type { BnsSearchDto, FacilitySearchDto } from "../model/bns-if-interface";
|
||||||
|
|
||||||
import { bnsIfService } from "./bns-if-service";
|
import { bnsIfService } from "./bns-if-service";
|
||||||
|
|
||||||
@@ -31,3 +31,12 @@ export function useEmCodeList(dpmCd?: string, lclgvrnmntCd?: string) {
|
|||||||
queryFn: () => bnsIfService.getEmCodeList(dpmCd, lclgvrnmntCd),
|
queryFn: () => bnsIfService.getEmCodeList(dpmCd, lclgvrnmntCd),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useFacilityList(params: FacilitySearchDto, options?: { enabled?: boolean }) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...bnsIfBaseKey, "getFacilityList", params],
|
||||||
|
queryFn: () => bnsIfService.getFacilityList(params),
|
||||||
|
enabled: options?.enabled,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export { bnsIfService } from "./api/bns-if-service";
|
export { bnsIfService } from "./api/bns-if-service";
|
||||||
export { useBnsDetail, useBnsList, useEmCodeList } from "./api/queries";
|
export { useBnsDetail, useBnsList, useEmCodeList, useFacilityList } from "./api/queries";
|
||||||
export { buildBnsSearchDto } from "./lib/build-bns-search-dto";
|
export { buildBnsSearchDto } from "./lib/build-bns-search-dto";
|
||||||
export type {
|
export type {
|
||||||
BnsDetailResponseDto,
|
BnsDetailResponseDto,
|
||||||
@@ -10,4 +10,7 @@ export type {
|
|||||||
BnsSearchDto,
|
BnsSearchDto,
|
||||||
EmCodeItemDto,
|
EmCodeItemDto,
|
||||||
EmCodeListResponseDto,
|
EmCodeListResponseDto,
|
||||||
|
FacilityListResponseWrapperDto,
|
||||||
|
FacilityResponseDto,
|
||||||
|
FacilitySearchDto,
|
||||||
} from "./model/bns-if-interface";
|
} from "./model/bns-if-interface";
|
||||||
|
|||||||
@@ -117,9 +117,89 @@ export interface EmCodeListResponseDto {
|
|||||||
ddtlList: EmCodeItemDto[];
|
ddtlList: EmCodeItemDto[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 백엔드 FacilitySearchDto */
|
||||||
|
export interface FacilitySearchDto {
|
||||||
|
sido?: string;
|
||||||
|
gugun?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 FacilityResponseDto */
|
||||||
|
export interface FacilityResponseDto {
|
||||||
|
bscFctCd?: string;
|
||||||
|
bscFctNm?: string;
|
||||||
|
addr?: string;
|
||||||
|
fctCpc?: string;
|
||||||
|
fctCpc1?: string;
|
||||||
|
fctCpc2?: string;
|
||||||
|
fctCpc3?: string;
|
||||||
|
totInflAmntAve?: unknown;
|
||||||
|
pInflAmntAve?: unknown;
|
||||||
|
oInflAmntAve?: unknown;
|
||||||
|
aInflAmntAve?: unknown;
|
||||||
|
totTputAve?: unknown;
|
||||||
|
pTputAve?: unknown;
|
||||||
|
oTputAve?: unknown;
|
||||||
|
aTputAve?: unknown;
|
||||||
|
inflAssctBfrWqltBod?: unknown;
|
||||||
|
efflWqltBod?: unknown;
|
||||||
|
inflAssctBfrWqltCod?: unknown;
|
||||||
|
efflWqltCod?: unknown;
|
||||||
|
inflAssctBfrWqltSs?: unknown;
|
||||||
|
efflWqltSs?: unknown;
|
||||||
|
inflAssctBfrWqltTn?: unknown;
|
||||||
|
efflWqltTn?: unknown;
|
||||||
|
inflAssctBfrWqltTp?: unknown;
|
||||||
|
efflWqltTp?: unknown;
|
||||||
|
inflAssctBfrWqltClfgrpcnt?: unknown;
|
||||||
|
efflWqltClfgrpcnt?: unknown;
|
||||||
|
tmEfficiency?: unknown;
|
||||||
|
pcLoad?: unknown;
|
||||||
|
trtFrfHanNm?: string;
|
||||||
|
applctNtcheNm?: string;
|
||||||
|
excretionsTot?: unknown;
|
||||||
|
stkrsngWstwtrTot?: unknown;
|
||||||
|
lchtTot?: unknown;
|
||||||
|
etcTot?: unknown;
|
||||||
|
tel?: string;
|
||||||
|
rnDt?: string;
|
||||||
|
compDt?: string;
|
||||||
|
tot?: unknown;
|
||||||
|
oprClsNm?: string;
|
||||||
|
trstOprEtpNm?: string;
|
||||||
|
yrstpTrstCst?: string;
|
||||||
|
trstOprDt?: string;
|
||||||
|
trstAfr?: string;
|
||||||
|
dsfctNm?: string;
|
||||||
|
rvNm?: string;
|
||||||
|
mnstr?: string;
|
||||||
|
wbsnNm?: string;
|
||||||
|
efflWqltStndrdNm?: string;
|
||||||
|
elcpwAmnt?: unknown;
|
||||||
|
toeRt?: unknown;
|
||||||
|
toeSum?: unknown;
|
||||||
|
co2Emissions?: unknown;
|
||||||
|
toeQnty?: unknown;
|
||||||
|
toeRt2?: unknown;
|
||||||
|
useQnty?: unknown;
|
||||||
|
eqpPrmsDt?: string;
|
||||||
|
mdmbsn?: string;
|
||||||
|
lgbsn?: string;
|
||||||
|
lgbsnBod?: string;
|
||||||
|
cntrTrtFctNm?: string;
|
||||||
|
totWtrQrm?: unknown;
|
||||||
|
inflAssctBfrWqltToc?: unknown;
|
||||||
|
efflWqltToc?: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 FacilityListResponseWrapperDto */
|
||||||
|
export interface FacilityListResponseWrapperDto {
|
||||||
|
fctList: FacilityResponseDto[];
|
||||||
|
}
|
||||||
|
|
||||||
/** Controller public 메서드명·시그니처와 동일 */
|
/** Controller public 메서드명·시그니처와 동일 */
|
||||||
export interface BnsIfService {
|
export interface BnsIfService {
|
||||||
getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto>;
|
getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto>;
|
||||||
getBnsDetail(gbn: string, yr: string, seqNo: string): Promise<BnsDetailResponseWrapperDto>;
|
getBnsDetail(gbn: string, yr: string, seqNo: string): Promise<BnsDetailResponseWrapperDto>;
|
||||||
getEmCodeList(dpmCd?: string, lclgvrnmntCd?: string): Promise<EmCodeListResponseDto>;
|
getEmCodeList(dpmCd?: string, lclgvrnmntCd?: string): Promise<EmCodeListResponseDto>;
|
||||||
|
getFacilityList(params: FacilitySearchDto): Promise<FacilityListResponseWrapperDto>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type { UserPostSearchParams } from "../model/post-interface";
|
||||||
|
|
||||||
|
import { postApi } from "./post-service";
|
||||||
|
|
||||||
|
export function useUserPostList(params?: UserPostSearchParams) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS, params],
|
||||||
|
queryFn: () => postApi.getUserPostList(params),
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { client } from "@/shared/api";
|
||||||
|
import { AS_DASHBOARD_BBS_ID, ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import { PostService, UserPostListResponse, UserPostSearchParams } from "../model/post-interface";
|
||||||
|
|
||||||
|
async function getUserPostList(params?: UserPostSearchParams): Promise<UserPostListResponse> {
|
||||||
|
const response = await client.get<UserPostListResponse>(
|
||||||
|
`${ENDPOINT.BOARD_SERVICE.POSTS}/user/${AS_DASHBOARD_BBS_ID}`,
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const postApi: PostService = {
|
||||||
|
getUserPostList,
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export { useUserPostList } from "./api/post-queries";
|
||||||
|
export { postApi } from "./api/post-service";
|
||||||
|
export type {
|
||||||
|
PostService,
|
||||||
|
UserPost,
|
||||||
|
UserPostListResponse,
|
||||||
|
UserPostSearchParams,
|
||||||
|
} from "./model/post-interface";
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { PageableParams, PageResponse } from "@/shared/model";
|
||||||
|
|
||||||
|
export interface UserPostSearchParams extends PageableParams {
|
||||||
|
keywordType?: string;
|
||||||
|
keyword?: string;
|
||||||
|
popupYn?: string;
|
||||||
|
upndFixYn?: string;
|
||||||
|
prdPstgYn?: string;
|
||||||
|
siteId?: string;
|
||||||
|
bbsTypeCd?: string;
|
||||||
|
ansUseYn?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserPost {
|
||||||
|
rowNo: number;
|
||||||
|
bbsId: number;
|
||||||
|
pstNo: number;
|
||||||
|
pstTtl: string;
|
||||||
|
pstCn: string;
|
||||||
|
inqNocs: number;
|
||||||
|
rfrncUrl: string;
|
||||||
|
popupYn: string;
|
||||||
|
upndFixYn: string;
|
||||||
|
prdPstgYn: string;
|
||||||
|
rootPath: string;
|
||||||
|
pstPath: string;
|
||||||
|
ansPstn: number;
|
||||||
|
wrtrNm: string;
|
||||||
|
wrtrCplc: string;
|
||||||
|
atflCd: string;
|
||||||
|
regDt: string;
|
||||||
|
rgtrNm: string;
|
||||||
|
cmntCnt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserPostListResponse = PageResponse<UserPost>;
|
||||||
|
|
||||||
|
export interface PostService {
|
||||||
|
getUserPostList(params?: UserPostSearchParams): Promise<UserPostListResponse>;
|
||||||
|
}
|
||||||
@@ -18,13 +18,7 @@ interface CodeSelectProps extends React.ComponentPropsWithoutRef<typeof Select>
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CodeSelect({
|
function CodeSelect({ upCd, useYn, placeholder = "선택", className, ...props }: CodeSelectProps) {
|
||||||
upCd,
|
|
||||||
useYn,
|
|
||||||
placeholder = "선택",
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: CodeSelectProps) {
|
|
||||||
const listQuery = useCodeList(useYn ? undefined : upCd);
|
const listQuery = useCodeList(useYn ? undefined : upCd);
|
||||||
const listByUseYnQuery = useCodeListByUseYn(useYn ? upCd : undefined, useYn ?? "");
|
const listByUseYnQuery = useCodeListByUseYn(useYn ? upCd : undefined, useYn ?? "");
|
||||||
|
|
||||||
@@ -34,7 +28,7 @@ function CodeSelect({
|
|||||||
return (
|
return (
|
||||||
<Select {...props} disabled={props.disabled || isLoading}>
|
<Select {...props} disabled={props.disabled || isLoading}>
|
||||||
<SelectTrigger className={cn("w-[12rem]", className)}>
|
<SelectTrigger className={cn("w-[12rem]", className)}>
|
||||||
<SelectValue placeholder={isLoading ? "로딩 중…" : placeholder} />
|
<SelectValue placeholder={placeholder} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{codes?.map((item) => (
|
{codes?.map((item) => (
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { client } from "@/shared/api";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
DashboardMainResponseDto,
|
||||||
|
DashboardMainSearchDto,
|
||||||
|
DashboardService,
|
||||||
|
} from "../model/dashboard-interface";
|
||||||
|
|
||||||
|
const BASE = ENDPOINT.AS_SERVICE.DASHBOARD;
|
||||||
|
|
||||||
|
async function getMainDashboard(
|
||||||
|
searchDto: DashboardMainSearchDto,
|
||||||
|
): Promise<DashboardMainResponseDto> {
|
||||||
|
const response = await client.get<DashboardMainResponseDto>(`${BASE}/main`, {
|
||||||
|
params: searchDto,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dashboardService = {
|
||||||
|
getMainDashboard,
|
||||||
|
} satisfies DashboardService;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type { DashboardMainSearchDto } from "../model/dashboard-interface";
|
||||||
|
|
||||||
|
import { dashboardService } from "./dashboard-service";
|
||||||
|
|
||||||
|
export const dashboardBaseKey = [ENDPOINT.AS_SERVICE.DASHBOARD] as const;
|
||||||
|
|
||||||
|
const EMPTY_SEARCH: DashboardMainSearchDto = {};
|
||||||
|
|
||||||
|
export function useGetMainDashboard(params: DashboardMainSearchDto = EMPTY_SEARCH) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...dashboardBaseKey, "getMainDashboard", params],
|
||||||
|
queryFn: () => dashboardService.getMainDashboard(params),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export { dashboardService } from "./api/dashboard-service";
|
||||||
|
export { dashboardBaseKey, useGetMainDashboard } from "./api/queries";
|
||||||
|
export type {
|
||||||
|
DashboardMainResponseDto,
|
||||||
|
DashboardMainSearchDto,
|
||||||
|
DashboardService,
|
||||||
|
DocPrgrsSttsResponseDto,
|
||||||
|
NotiAsStatResponseDto,
|
||||||
|
NotiAsUserResponseDto,
|
||||||
|
} from "./model/dashboard-interface";
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/** 백엔드 DashboardMainSearchDto */
|
||||||
|
export interface DashboardMainSearchDto {
|
||||||
|
searchYear?: number;
|
||||||
|
notiType?: string;
|
||||||
|
notReadYn?: "Y" | "N";
|
||||||
|
startDt?: string;
|
||||||
|
endDt?: string;
|
||||||
|
docSeComCd?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 DocPrgrsSttsResponseDto */
|
||||||
|
export interface DocPrgrsSttsResponseDto {
|
||||||
|
docSeNm: string;
|
||||||
|
docSeComCd: string;
|
||||||
|
reqCnt: number;
|
||||||
|
prgrsCnt: number;
|
||||||
|
cmptnCnt: number;
|
||||||
|
totCnt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 NotiAsUserResponseDto */
|
||||||
|
export interface NotiAsUserResponseDto {
|
||||||
|
notiId: number;
|
||||||
|
recvUnqId: string;
|
||||||
|
senderUnqId: string;
|
||||||
|
notiType: string;
|
||||||
|
notiTtl: string;
|
||||||
|
notiCn?: string;
|
||||||
|
notReadYn?: "Y" | "N";
|
||||||
|
regDt?: string;
|
||||||
|
notiUrl?: string;
|
||||||
|
notiLnkgUnqNo?: string;
|
||||||
|
docSeComCd?: string;
|
||||||
|
docSeComNm?: string;
|
||||||
|
notiIdntyDt?: string;
|
||||||
|
notiIdntyYn?: "Y" | "N";
|
||||||
|
notiPrcsCmptnYn?: "Y" | "N";
|
||||||
|
notiPrcsCmptnDt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 NotiAsStatResponseDto */
|
||||||
|
export interface NotiAsStatResponseDto {
|
||||||
|
todayCount: number;
|
||||||
|
delayCount: number;
|
||||||
|
pendingCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 백엔드 DashboardMainResponseDto */
|
||||||
|
export interface DashboardMainResponseDto {
|
||||||
|
docPrgrsSttsList: DocPrgrsSttsResponseDto[];
|
||||||
|
notiList: NotiAsUserResponseDto[];
|
||||||
|
notiStats: NotiAsStatResponseDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Controller public 메서드명·시그니처와 동일 */
|
||||||
|
export interface DashboardService {
|
||||||
|
getMainDashboard(searchDto: DashboardMainSearchDto): Promise<DashboardMainResponseDto>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { client } from "@/shared/api";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ClsfSystmParamsDto,
|
||||||
|
ClsfSystmResponseDto,
|
||||||
|
DcService,
|
||||||
|
FileDownloadParamsDto,
|
||||||
|
FileParamsDto,
|
||||||
|
LvNowDmgMtncGnsvyResponseDto,
|
||||||
|
LvNowDmgMtncGnsvySearchDto,
|
||||||
|
NowDmgMtncGnsvyResponseDto,
|
||||||
|
NowDmgMtncGnsvySearchDto,
|
||||||
|
} from "../model/dc-interface";
|
||||||
|
|
||||||
|
async function getNowDmgMtncGnsvy(
|
||||||
|
params: NowDmgMtncGnsvySearchDto,
|
||||||
|
): Promise<NowDmgMtncGnsvyResponseDto[]> {
|
||||||
|
const response = await client.get<NowDmgMtncGnsvyResponseDto[]>(
|
||||||
|
`${ENDPOINT.DC_SERVICE}/nowDmgMtncGnsvy`,
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLvNowDmgMtncGnsvy(
|
||||||
|
params: LvNowDmgMtncGnsvySearchDto,
|
||||||
|
): Promise<LvNowDmgMtncGnsvyResponseDto[]> {
|
||||||
|
const response = await client.get<LvNowDmgMtncGnsvyResponseDto[]>(
|
||||||
|
`${ENDPOINT.DC_SERVICE}/lvNowDmgMtncGnsvy`,
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getClsfSystm(params: ClsfSystmParamsDto): Promise<ClsfSystmResponseDto[]> {
|
||||||
|
const response = await client.get<ClsfSystmResponseDto[]>(`${ENDPOINT.DC_SERVICE}/clsf`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFile(params: FileParamsDto): Promise<unknown> {
|
||||||
|
const response = await client.get<unknown>(`${ENDPOINT.DC_SERVICE}/file`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFilePrcs(fileSn: string): Promise<unknown> {
|
||||||
|
const response = await client.get<unknown>(`${ENDPOINT.DC_SERVICE}/fileprcs`, {
|
||||||
|
params: { fileSn },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileDownload(params: FileDownloadParamsDto): Promise<Blob> {
|
||||||
|
const response = await client.get<Blob>(`${ENDPOINT.DC_SERVICE}/fileDownload`, {
|
||||||
|
params,
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dcService = {
|
||||||
|
getNowDmgMtncGnsvy,
|
||||||
|
getLvNowDmgMtncGnsvy,
|
||||||
|
getClsfSystm,
|
||||||
|
getFile,
|
||||||
|
getFilePrcs,
|
||||||
|
fileDownload,
|
||||||
|
} satisfies DcService;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ClsfSystmParamsDto,
|
||||||
|
FileParamsDto,
|
||||||
|
LvNowDmgMtncGnsvySearchDto,
|
||||||
|
NowDmgMtncGnsvySearchDto,
|
||||||
|
} from "../model/dc-interface";
|
||||||
|
|
||||||
|
import { dcService } from "./dc-service";
|
||||||
|
|
||||||
|
export const baseKey = [ENDPOINT.DC_SERVICE] as const;
|
||||||
|
|
||||||
|
export function useNowDmgMtncGnsvy(params: NowDmgMtncGnsvySearchDto, enabled?: boolean) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...baseKey, "nowDmgMtncGnsvy", params],
|
||||||
|
queryFn: () => dcService.getNowDmgMtncGnsvy(params),
|
||||||
|
enabled: enabled ?? Boolean(params.swtrmtFcltCd && params.ctpvNm),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLvNowDmgMtncGnsvyEnabled(params: LvNowDmgMtncGnsvySearchDto): boolean {
|
||||||
|
if (!params.level || !params.ctpvNm) return false;
|
||||||
|
if (params.level === "1") return true;
|
||||||
|
if (params.level === "2") return Boolean(params.sggNm);
|
||||||
|
if (params.level === "3") return Boolean(params.sggNm && params.gnsvyYr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLvNowDmgMtncGnsvy(params: LvNowDmgMtncGnsvySearchDto) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...baseKey, "lvNowDmgMtncGnsvy", params],
|
||||||
|
queryFn: () => dcService.getLvNowDmgMtncGnsvy(params),
|
||||||
|
enabled: isLvNowDmgMtncGnsvyEnabled(params),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useClsfSystm(params: ClsfSystmParamsDto) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...baseKey, "clsfSystm", params],
|
||||||
|
queryFn: () => dcService.getClsfSystm(params),
|
||||||
|
// TODO: T4 — upClsfSystmSn 점진 로드 여부 확정 후 enabled/파라미터 조정
|
||||||
|
enabled: Boolean(params.clsfSystmNm),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFile(params: FileParamsDto) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...baseKey, "file", params],
|
||||||
|
queryFn: () => dcService.getFile(params),
|
||||||
|
// TODO: T2 — getFile Response DTO 확정 후 타입 교체
|
||||||
|
enabled: Boolean(params.clsfSystmSn),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFilePrcs(fileSn: string) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...baseKey, "filePrcs", fileSn],
|
||||||
|
queryFn: () => dcService.getFilePrcs(fileSn),
|
||||||
|
// TODO: T2 — getFilePrcs Response DTO 확정 후 타입 교체
|
||||||
|
enabled: Boolean(fileSn),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export { dcService } from "./api/dc-service";
|
||||||
|
export {
|
||||||
|
baseKey as nowDmgMtncGnsvyBaseKey,
|
||||||
|
useClsfSystm,
|
||||||
|
useFile,
|
||||||
|
useFilePrcs,
|
||||||
|
useLvNowDmgMtncGnsvy,
|
||||||
|
useNowDmgMtncGnsvy,
|
||||||
|
} from "./api/queries";
|
||||||
|
export type {
|
||||||
|
ClsfSystmParamsDto,
|
||||||
|
ClsfSystmResponseDto,
|
||||||
|
FileDownloadParamsDto,
|
||||||
|
FileParamsDto,
|
||||||
|
LvNowDmgMtncGnsvyResponseDto,
|
||||||
|
LvNowDmgMtncGnsvySearchDto,
|
||||||
|
NowDmgMtncGnsvyResponseDto,
|
||||||
|
NowDmgMtncGnsvySearchDto,
|
||||||
|
} from "./model/dc-interface";
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
export interface NowDmgMtncGnsvySearchDto {
|
||||||
|
swtrmtFcltCd?: string;
|
||||||
|
ctpvNm?: string;
|
||||||
|
sggNm?: string;
|
||||||
|
targetYear?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LvNowDmgMtncGnsvySearchDto {
|
||||||
|
level?: string;
|
||||||
|
ctpvNm?: string;
|
||||||
|
sggNm?: string;
|
||||||
|
gnsvyYr?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClsfSystmParamsDto {
|
||||||
|
clsfSystmNm?: string;
|
||||||
|
upClsfSystmSn?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileParamsDto {
|
||||||
|
sort?: string;
|
||||||
|
clsfSystmSn?: string;
|
||||||
|
vetPrcsYn?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileDownloadParamsDto {
|
||||||
|
filePath?: string;
|
||||||
|
fileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type NullableNumber = number | null;
|
||||||
|
type NullableString = string | null;
|
||||||
|
|
||||||
|
/** 현재·단계별(Dt/Dc) 공통 지표 필드 */
|
||||||
|
interface NowDmgMtncGnsvyMetricFields {
|
||||||
|
planYr?: NullableString;
|
||||||
|
swtrmtPpltnCnt?: NullableNumber;
|
||||||
|
natrSwtrmtPpltnCnt?: NullableNumber;
|
||||||
|
scilSwtrmtPpltnCnt?: NullableNumber;
|
||||||
|
swtrmtZoneArea?: NullableNumber;
|
||||||
|
wtrUsqtyOrgnUnit?: NullableNumber;
|
||||||
|
drtwSwtcRt?: NullableNumber;
|
||||||
|
flctnLodRt?: NullableNumber;
|
||||||
|
drtwOrgnUnit?: NullableNumber;
|
||||||
|
planSwageQntSum?: NullableNumber;
|
||||||
|
lvngDrtwSum?: NullableNumber;
|
||||||
|
ugwtSum?: NullableNumber;
|
||||||
|
fctryWwtSum?: NullableNumber;
|
||||||
|
toursmDrtwSum?: NullableNumber;
|
||||||
|
mtbsSum?: NullableNumber;
|
||||||
|
linkPrcsCntSum?: NullableNumber;
|
||||||
|
etcDrtwSum?: NullableNumber;
|
||||||
|
ugwtUseSum?: NullableNumber;
|
||||||
|
fcltCpctSum?: NullableNumber;
|
||||||
|
exclsMethExpln?: NullableString;
|
||||||
|
brcPplSum?: NullableNumber;
|
||||||
|
icptgPplSum?: NullableNumber;
|
||||||
|
exclnPplSum?: NullableNumber;
|
||||||
|
stofcbrSum?: NullableNumber;
|
||||||
|
pmstnSum?: NullableNumber;
|
||||||
|
simplPubSwtrmtFcltSum?: NullableNumber;
|
||||||
|
swageImpdFcltSum?: NullableNumber;
|
||||||
|
mltvFclSum?: NullableNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StageSuffix = "Dt1" | "Dt2" | "Dt3" | "Dt4" | "Dc1" | "Dc2" | "Dc3" | "Dc4";
|
||||||
|
|
||||||
|
type StagedMetricFields = {
|
||||||
|
[K in keyof NowDmgMtncGnsvyMetricFields as `${K & string}${StageSuffix}`]?: NowDmgMtncGnsvyMetricFields[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface NowDmgMtncGnsvyResponseDto
|
||||||
|
extends NowDmgMtncGnsvyMetricFields, StagedMetricFields {
|
||||||
|
swtrmtFcltCd?: NullableString;
|
||||||
|
swtrmtFcltNm?: NullableString;
|
||||||
|
gnsvyYr?: NullableString;
|
||||||
|
ctpvNm?: NullableString;
|
||||||
|
sggNm?: NullableString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lvNowDmgMtncGnsvy 계층 노드.
|
||||||
|
* - level 1: ctpvNm + children(구군)
|
||||||
|
* - level 2: sggNm + children(연도)
|
||||||
|
* - level 3: gnsvyYr + children(처리시설)
|
||||||
|
* - 시설 리프: swtrmtFcltCd / swtrmtFcltNm (level 없음)
|
||||||
|
*/
|
||||||
|
export interface LvNowDmgMtncGnsvyResponseDto {
|
||||||
|
level?: number;
|
||||||
|
ctpvNm?: string;
|
||||||
|
sggNm?: string;
|
||||||
|
gnsvyYr?: string;
|
||||||
|
swtrmtFcltCd?: string;
|
||||||
|
swtrmtFcltNm?: string;
|
||||||
|
children?: LvNowDmgMtncGnsvyResponseDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClsfSystmResponseDto {
|
||||||
|
clsfSystmSn: string;
|
||||||
|
upClsfSystmSn: string;
|
||||||
|
clsfSystmNm: string;
|
||||||
|
sortSeq: string;
|
||||||
|
droppAble: boolean;
|
||||||
|
path: string | null;
|
||||||
|
children: ClsfSystmResponseDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DcService {
|
||||||
|
getNowDmgMtncGnsvy(params: NowDmgMtncGnsvySearchDto): Promise<NowDmgMtncGnsvyResponseDto[]>;
|
||||||
|
getLvNowDmgMtncGnsvy(params: LvNowDmgMtncGnsvySearchDto): Promise<LvNowDmgMtncGnsvyResponseDto[]>;
|
||||||
|
getClsfSystm(params: ClsfSystmParamsDto): Promise<ClsfSystmResponseDto[]>;
|
||||||
|
getFile(params: FileParamsDto): Promise<unknown>;
|
||||||
|
getFilePrcs(fileSn: string): Promise<unknown>;
|
||||||
|
fileDownload(params: FileDownloadParamsDto): Promise<Blob>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { client } from "@/shared/api";
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
DcRagApiToService,
|
||||||
|
DcRagCommonResponseDto,
|
||||||
|
DcRagDraftGenerateResponseDto,
|
||||||
|
DcRagDraftStatusResponseDto,
|
||||||
|
} from "../model/dcrag-to-interface";
|
||||||
|
import type {
|
||||||
|
DcRagCostVerifyRequestDto,
|
||||||
|
DcRagDocumentTransferRequestDto,
|
||||||
|
DcRagDraftGenerateRequestDto,
|
||||||
|
} from "../model/dcrag-to-payload-schema";
|
||||||
|
|
||||||
|
const BASE = ENDPOINT.AS_SERVICE.DCRAG_TO;
|
||||||
|
|
||||||
|
async function requestDocumentTransfer(
|
||||||
|
body: DcRagDocumentTransferRequestDto,
|
||||||
|
): Promise<DcRagCommonResponseDto> {
|
||||||
|
const response = await client.post<DcRagCommonResponseDto>(`${BASE}/documents/transfer`, body);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestDraftGeneration(
|
||||||
|
body: DcRagDraftGenerateRequestDto,
|
||||||
|
): Promise<DcRagDraftGenerateResponseDto> {
|
||||||
|
const response = await client.post<DcRagDraftGenerateResponseDto>(
|
||||||
|
`${BASE}/documents/draft/generate`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDraftGenerationStatus(crtDmndUuid: string): Promise<DcRagDraftStatusResponseDto> {
|
||||||
|
const response = await client.get<DcRagDraftStatusResponseDto>(
|
||||||
|
`${BASE}/documents/draft/status/${crtDmndUuid}`,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestCostVerification(
|
||||||
|
body: DcRagCostVerifyRequestDto,
|
||||||
|
): Promise<DcRagCommonResponseDto> {
|
||||||
|
const response = await client.post<DcRagCommonResponseDto>(`${BASE}/documents/cost/verify`, body);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reTransferDocument(
|
||||||
|
body: DcRagDocumentTransferRequestDto,
|
||||||
|
): Promise<DcRagCommonResponseDto> {
|
||||||
|
const response = await client.post<DcRagCommonResponseDto>(
|
||||||
|
`${BASE}/documents/re-transfer`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reGenerateDraft(
|
||||||
|
body: DcRagDraftGenerateRequestDto,
|
||||||
|
): Promise<DcRagDraftGenerateResponseDto> {
|
||||||
|
const response = await client.post<DcRagDraftGenerateResponseDto>(
|
||||||
|
`${BASE}/documents/draft/re-generate`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reVerifyCost(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto> {
|
||||||
|
const response = await client.post<DcRagCommonResponseDto>(
|
||||||
|
`${BASE}/documents/cost/re-verify`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dcRagApiToService = {
|
||||||
|
requestDocumentTransfer,
|
||||||
|
requestDraftGeneration,
|
||||||
|
getDraftGenerationStatus,
|
||||||
|
requestCostVerification,
|
||||||
|
reTransferDocument,
|
||||||
|
reGenerateDraft,
|
||||||
|
reVerifyCost,
|
||||||
|
} satisfies DcRagApiToService;
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
import { useMutationWrapper } from "@/shared/lib";
|
||||||
|
import { aiAutoPrcsMngKeys } from "@/entities/ai-auto-prcs-mng";
|
||||||
|
import { fnrsCnsltnKeys } from "@/entities/fnrs-cnsltn";
|
||||||
|
|
||||||
|
import type { DcRagDraftStatusResponseDto } from "../model/dcrag-to-interface";
|
||||||
|
import type {
|
||||||
|
DcRagCostVerifyRequestDto,
|
||||||
|
DcRagDocumentTransferRequestDto,
|
||||||
|
DcRagDraftGenerateRequestDto,
|
||||||
|
} from "../model/dcrag-to-payload-schema";
|
||||||
|
|
||||||
|
import { dcRagApiToService } from "./dcrag-to-service";
|
||||||
|
|
||||||
|
export const dcRagApiToBaseKey = [ENDPOINT.AS_SERVICE.DCRAG_TO] as const;
|
||||||
|
|
||||||
|
const DRAFT_STATUS_POLL_INTERVAL_MS = 5_000;
|
||||||
|
|
||||||
|
function isDraftGenerationSettled(data: DcRagDraftStatusResponseDto | undefined): boolean {
|
||||||
|
if (!data) return false;
|
||||||
|
if (data.status === "COMPLETED" || data.status === "FAILED") return true;
|
||||||
|
return data.events?.some((event) => event.eventType === "COMPLETED") ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateAfterAiRetry(
|
||||||
|
queryClient: ReturnType<typeof useQueryClient>,
|
||||||
|
fnrsCnsltnDocNo?: string,
|
||||||
|
) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: dcRagApiToBaseKey });
|
||||||
|
queryClient.invalidateQueries({ queryKey: aiAutoPrcsMngKeys.all });
|
||||||
|
if (fnrsCnsltnDocNo) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.detail(fnrsCnsltnDocNo) });
|
||||||
|
queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDraftGenerationStatus(crtDmndUuid: string | null | undefined) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [...dcRagApiToBaseKey, "getDraftGenerationStatus", crtDmndUuid],
|
||||||
|
queryFn: () => dcRagApiToService.getDraftGenerationStatus(crtDmndUuid!),
|
||||||
|
enabled: Boolean(crtDmndUuid),
|
||||||
|
refetchInterval: (query) =>
|
||||||
|
isDraftGenerationSettled(query.state.data) ? false : DRAFT_STATUS_POLL_INTERVAL_MS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRequestDocumentTransfer() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (body: DcRagDocumentTransferRequestDto) =>
|
||||||
|
dcRagApiToService.requestDocumentTransfer(body),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: dcRagApiToBaseKey });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRequestDraftGeneration() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (body: DcRagDraftGenerateRequestDto) =>
|
||||||
|
dcRagApiToService.requestDraftGeneration(body),
|
||||||
|
onSuccess: (_data, body) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: dcRagApiToBaseKey });
|
||||||
|
if (body.crtDmndUuid) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [...dcRagApiToBaseKey, "getDraftGenerationStatus", body.crtDmndUuid],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRequestCostVerification() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (body: DcRagCostVerifyRequestDto) =>
|
||||||
|
dcRagApiToService.requestCostVerification(body),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: dcRagApiToBaseKey });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReTransferDocument() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutationWrapper({
|
||||||
|
mutationFn: (body: DcRagDocumentTransferRequestDto) =>
|
||||||
|
dcRagApiToService.reTransferDocument(body),
|
||||||
|
onSuccess: (_data, body) => {
|
||||||
|
invalidateAfterAiRetry(queryClient, body.fnrsCnsltnDocNo);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReGenerateDraft() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutationWrapper({
|
||||||
|
mutationFn: (body: DcRagDraftGenerateRequestDto) => dcRagApiToService.reGenerateDraft(body),
|
||||||
|
onSuccess: (_data, body) => {
|
||||||
|
invalidateAfterAiRetry(queryClient, body.fnrsCnsltnDocNo);
|
||||||
|
if (body.crtDmndUuid) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [...dcRagApiToBaseKey, "getDraftGenerationStatus", body.crtDmndUuid],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useReVerifyCost() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutationWrapper({
|
||||||
|
mutationFn: (body: DcRagCostVerifyRequestDto) => dcRagApiToService.reVerifyCost(body),
|
||||||
|
onSuccess: (_data, body) => {
|
||||||
|
invalidateAfterAiRetry(queryClient, body.fnrsCnsltnDocNo);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export { dcRagApiToService } from "./api/dcrag-to-service";
|
||||||
|
export { dcRagApiToBaseKey } from "./api/queries";
|
||||||
|
export {
|
||||||
|
useDraftGenerationStatus,
|
||||||
|
useReGenerateDraft,
|
||||||
|
useRequestCostVerification,
|
||||||
|
useRequestDocumentTransfer,
|
||||||
|
useRequestDraftGeneration,
|
||||||
|
useReTransferDocument,
|
||||||
|
useReVerifyCost,
|
||||||
|
} from "./api/queries";
|
||||||
|
export type {
|
||||||
|
DcRagApiToService,
|
||||||
|
DcRagCommonResponseDto,
|
||||||
|
DcRagDraftGenerateResponseDto,
|
||||||
|
DcRagDraftStatusResponseDto,
|
||||||
|
EventInfoDto,
|
||||||
|
} from "./model/dcrag-to-interface";
|
||||||
|
export type {
|
||||||
|
DcRagCostVerifyRequestDto,
|
||||||
|
DcRagDocumentTransferRequestDto,
|
||||||
|
DcRagDraftGenerateRequestDto,
|
||||||
|
DcRagFileInfoDto,
|
||||||
|
} from "./model/dcrag-to-payload-schema";
|
||||||
|
export {
|
||||||
|
dcRagCostVerifyRequestSchema,
|
||||||
|
dcRagDocumentTransferRequestSchema,
|
||||||
|
dcRagDraftGenerateRequestSchema,
|
||||||
|
dcRagFileInfoSchema,
|
||||||
|
} from "./model/dcrag-to-payload-schema";
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user