Compare commits
505
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be14fe2603 | ||
|
|
d7f101ee30 | ||
|
|
35b9f813c4 | ||
|
|
aafb326662 | ||
|
|
aa1b309b26 | ||
|
|
bf0aef59cd | ||
|
|
b2ddcd0cf3 | ||
|
|
49ace44c9b | ||
|
|
91576f401b | ||
|
|
febcd18eda | ||
|
|
8cc9f0d291 | ||
|
|
a8f2c8f41a | ||
|
|
4d685d5a67 | ||
|
|
96196229f4 | ||
|
|
94b2e84c10 | ||
|
|
80d2230c9a | ||
|
|
d5f0c3bc95 | ||
|
|
b7134cb3e4 | ||
|
|
ec78658e46 | ||
|
|
44361af392 | ||
|
|
76130caa6d | ||
|
|
352a78a0c5 | ||
|
|
65bee9586d | ||
|
|
31cda8579a | ||
|
|
1dc40b9efc | ||
|
|
6cb3d8ad65 | ||
|
|
225e2732d1 | ||
|
|
d82ae99b6a | ||
|
|
23341b93c7 | ||
|
|
b99f4904b0 | ||
|
|
f742feca4b | ||
|
|
569e003827 | ||
|
|
2db5dd59af | ||
|
|
8a8504dca9 | ||
|
|
7e82c6a802 | ||
|
|
bdc6251474 | ||
|
|
479cf5c4a0 | ||
|
|
2b3502161c | ||
|
|
0544a72011 | ||
|
|
34ba09ce1b | ||
|
|
aa5a71ae68 | ||
|
|
9067e645b3 | ||
|
|
10d3fc096a | ||
|
|
7fd95ba6f9 | ||
|
|
e707637c88 | ||
|
|
5da89e8ee7 | ||
|
|
f9ff279895 | ||
|
|
4cde9b67c1 | ||
|
|
80d93c1631 | ||
|
|
872b3d08c0 | ||
|
|
cb191817a8 | ||
|
|
8198fda45b | ||
|
|
37374cb754 | ||
|
|
9e36edec7f | ||
|
|
3204bba71b | ||
|
|
3cdcf6a6f7 | ||
|
|
922861c872 | ||
|
|
4a3945f1e4 | ||
|
|
a1caa1b860 | ||
|
|
166dea2b86 | ||
|
|
3f7262e213 | ||
|
|
231ee10ff8 | ||
|
|
5aa9ab4f39 | ||
|
|
8c46f7ca06 | ||
|
|
23e15daa37 | ||
|
|
dd56ed60ad | ||
|
|
18129905d8 | ||
|
|
d03263a3c6 | ||
|
|
be46bcbd17 | ||
|
|
201dbf577f | ||
|
|
884dd71ca7 | ||
|
|
70efdc9db5 | ||
|
|
36549e7281 | ||
|
|
c1ffc33834 | ||
|
|
2c3961644c | ||
|
|
2fe3631a0d | ||
|
|
e0cdba00be | ||
|
|
5e206dd210 | ||
|
|
2008a5a9ee | ||
|
|
53aeeccc2a | ||
|
|
c7738a6d6c | ||
|
|
baf29df950 | ||
|
|
0a13f34092 | ||
|
|
0b45361135 | ||
|
|
83420c76b8 | ||
|
|
42f83bcd24 | ||
|
|
ccfbe6ae32 | ||
|
|
8126fb844a | ||
|
|
23c9ddf4ce | ||
|
|
63388ccc90 | ||
|
|
bda3228d0f | ||
|
|
95ca40913f | ||
|
|
8bfe1c9ab2 | ||
|
|
0dc981819f | ||
|
|
4b7ada9362 | ||
|
|
71c5dfb8dd | ||
|
|
7e3911dbd6 | ||
|
|
839d31b9ca | ||
|
|
386fdade59 | ||
|
|
0cbbc3b934 | ||
|
|
f5051225ea | ||
|
|
f7218fc98a | ||
|
|
c9e3ff0d91 | ||
|
|
474ad1ab26 | ||
|
|
099e000f88 | ||
|
|
7fa4538667 | ||
|
|
91e11a7857 | ||
|
|
d582293e5f | ||
|
|
d8c5344451 | ||
|
|
d23a16905d | ||
|
|
2649ce286b | ||
|
|
2f9e97b65b | ||
|
|
e1940043e9 | ||
|
|
7f1c804a0c | ||
|
|
d07bb6074d | ||
|
|
4109691eea | ||
|
|
4eaac5dbf1 | ||
|
|
b99fe8daf1 | ||
|
|
3f4e3581dd | ||
|
|
f00627270f | ||
|
|
1858739bb9 | ||
|
|
3d320d0e19 | ||
|
|
9d2c0afb98 | ||
|
|
29f2a9169e | ||
|
|
493acc9ebd | ||
|
|
d3bdc58994 | ||
|
|
20efccb4d9 | ||
|
|
f58daf3bac | ||
|
|
b42810d29a | ||
|
|
2e6a75141b | ||
|
|
ce60e5f5fb | ||
|
|
c9d85a8ce4 | ||
|
|
0f8b6cd6d0 | ||
|
|
b11658e1d6 | ||
|
|
bcf5d3caec | ||
|
|
474676af17 | ||
|
|
3cc0e9de14 | ||
|
|
ff7be74437 | ||
|
|
f52f22e80f | ||
|
|
86e00d48dc | ||
|
|
bf61f6f76a | ||
|
|
62c41297c4 | ||
|
|
09b5cc3798 | ||
|
|
155a727a77 | ||
|
|
a52dacf500 | ||
|
|
e8e38310ee | ||
|
|
2652e2cfe0 | ||
|
|
5a76ae8a88 | ||
|
|
7d9b97aac9 | ||
|
|
69c3183e35 | ||
|
|
5e6477cddd | ||
|
|
c13cab394a | ||
|
|
73f1940cb0 | ||
|
|
8a35d232ef | ||
|
|
728981cfe6 | ||
|
|
11cb2b9044 | ||
|
|
c1d6cd7b8e | ||
|
|
477426c384 | ||
|
|
ec60a05b01 | ||
|
|
90f0e4691b | ||
|
|
b41a317788 | ||
|
|
559e4ee352 | ||
|
|
a1d43dc65c | ||
|
|
d26cd90b02 | ||
|
|
45f72f7333 | ||
|
|
196d31a084 | ||
|
|
2f0448a98e | ||
|
|
5e0693e057 | ||
|
|
1708555133 | ||
|
|
c38d95fd60 | ||
|
|
667f7b2094 | ||
|
|
d17909df41 | ||
|
|
88113fca1c | ||
|
|
81fc947c9d | ||
|
|
c684440e65 | ||
|
|
4c6222acf1 | ||
|
|
2131ecc315 | ||
|
|
f53c72f31f | ||
|
|
1d3910c157 | ||
|
|
3b2f5c2d5d | ||
|
|
903cdaad45 | ||
|
|
386fc61a3f | ||
|
|
6e354690c5 | ||
|
|
adae670318 | ||
|
|
bdbefbf600 | ||
|
|
939089a79c | ||
|
|
f8354344ff | ||
|
|
e596c1f0a6 | ||
|
|
7f53badcf3 | ||
|
|
1dcf01bb0e | ||
|
|
eec25ab1c4 | ||
|
|
f90f093385 | ||
|
|
befe56fd44 | ||
|
|
47d91424c5 | ||
|
|
ddd72f71d2 | ||
|
|
1a1ff2ec30 | ||
|
|
10d89279bc | ||
|
|
a6f9ba7526 | ||
|
|
19dfe45b0b | ||
|
|
f584e21480 | ||
|
|
6acacbfcf4 | ||
|
|
52a79eca60 | ||
|
|
70faadb27e | ||
|
|
2eb1809f36 | ||
|
|
d9febf8010 | ||
|
|
d4c5787360 | ||
|
|
c8c461e8e8 | ||
|
|
9e2da003f1 | ||
|
|
ae41483bb7 | ||
|
|
d062b8a7d4 | ||
|
|
18825dd0c3 | ||
|
|
6b2ea58b66 | ||
|
|
fdfb37d4d0 | ||
|
|
450de39f8c | ||
|
|
c87ad8ea1b | ||
|
|
b336651503 | ||
|
|
7a1949fc6f | ||
|
|
35a63d7c5c | ||
|
|
031f5e2ed8 | ||
|
|
189b4b7599 | ||
|
|
6ada32cb9f | ||
|
|
5491c524c5 | ||
|
|
8d29f25101 | ||
|
|
28d4b2d831 | ||
|
|
399645a80a | ||
|
|
9d13809d01 | ||
|
|
e8c8db7383 | ||
|
|
b41814ed0c | ||
|
|
d71f2043de | ||
|
|
87ba3e8fa2 | ||
|
|
eabbbf456f | ||
|
|
ebfba7ac8c | ||
|
|
fe4a4a420e | ||
|
|
14ae432833 | ||
|
|
009a38f3ca | ||
|
|
cfc73e4094 | ||
|
|
748be6faa5 | ||
|
|
330e2e5c69 | ||
|
|
343a3f81bf | ||
|
|
13c7d5a0c5 | ||
|
|
a9c38b356a | ||
|
|
4181257afd | ||
|
|
0141ed56c2 | ||
|
|
35dc205810 | ||
|
|
d10a17eaf6 | ||
|
|
a623c53fa5 | ||
|
|
3db8babee7 | ||
|
|
ca7ac992e2 | ||
|
|
a956c9894e | ||
|
|
6c84ba6aeb | ||
|
|
e99b7d8162 | ||
|
|
09bb613929 | ||
|
|
761b0dd502 | ||
|
|
8c72cb3b72 | ||
|
|
35b6f4ae54 | ||
|
|
cf5ade639a | ||
|
|
0dfd5efa97 | ||
|
|
e6cbb9dfd8 | ||
|
|
52c7215cd8 | ||
|
|
fe88732a80 | ||
|
|
aa5f85a3c1 | ||
|
|
843519c3b5 | ||
|
|
c74917d1d9 | ||
|
|
9dca199d78 | ||
|
|
4fa7542aca | ||
|
|
1cd24b5dd5 | ||
|
|
34f1bf5e6a | ||
|
|
675e5f58d1 | ||
|
|
127fb41145 | ||
|
|
8abf32bb53 | ||
|
|
b567e5df13 | ||
|
|
40acdafed2 | ||
|
|
7f6b05e4e5 | ||
|
|
0d4a698c11 | ||
|
|
5bacfb04b5 | ||
|
|
1e8514a0bc | ||
|
|
b6714dd8a4 | ||
|
|
e05c2a03e0 | ||
|
|
914df505ae | ||
|
|
7e59ae6b66 | ||
|
|
eb84a23d7e | ||
|
|
1076120516 | ||
|
|
54ce26adbc | ||
|
|
28d54b615b | ||
|
|
424fa576fb | ||
|
|
7f8e5fe9f9 | ||
|
|
47f1bd012c | ||
|
|
0c2e762cee | ||
|
|
68124e6abb | ||
|
|
5587e6b6a3 | ||
|
|
ac1e98fbaa | ||
|
|
d25f9f3a01 | ||
|
|
fb638752eb | ||
|
|
8c3dd62f6d | ||
|
|
e79635b81a | ||
|
|
e3a9851de6 | ||
|
|
34811d4571 | ||
|
|
3167a9a6e3 | ||
|
|
a29d1f0cc2 | ||
|
|
e76cddd45b | ||
|
|
3c214f4725 | ||
|
|
80861938f3 | ||
|
|
401f2dba13 | ||
|
|
c4d7c516ef | ||
|
|
2f11b1185b | ||
|
|
42274a71b7 | ||
|
|
f1fcfa9ee4 | ||
|
|
afcfbbbf61 | ||
|
|
defba2962d | ||
|
|
4c7666169c | ||
|
|
5580b4301f | ||
|
|
421ff8cb40 | ||
|
|
96b95e0eb9 | ||
|
|
cea2d39df8 | ||
|
|
f4eae0d33e | ||
|
|
716a20cc22 | ||
|
|
873a74492a | ||
|
|
516d93d6fa | ||
|
|
958a8ffe2c | ||
|
|
0286e2a92e | ||
|
|
01e339cc61 | ||
|
|
0e7c9b4a96 | ||
|
|
d5814043d9 | ||
|
|
e81210ec04 | ||
|
|
8d17623d3a | ||
|
|
ba9d7c8172 | ||
|
|
06f5a3cde0 | ||
|
|
25417dcda1 | ||
|
|
9a3b332130 | ||
|
|
1236a24b61 | ||
|
|
f50de743e3 | ||
|
|
0c2568aad0 | ||
|
|
b15cbeb568 | ||
|
|
afdc06638f | ||
|
|
6fb610d9aa | ||
|
|
c14d536133 | ||
|
|
3008ae2eeb | ||
|
|
3b4d9df0e8 | ||
|
|
48d4a5e135 | ||
|
|
ff33565513 | ||
|
|
b40bae1b50 | ||
|
|
0533d229e6 | ||
|
|
bbb755bf56 | ||
|
|
31caee87db | ||
|
|
87cda451f3 | ||
|
|
e2b8f901a3 | ||
|
|
951e2c1f89 | ||
|
|
20fd291cc3 | ||
|
|
ee3ddce41d | ||
|
|
fb160e712b | ||
|
|
9a6cc1779f | ||
|
|
b459d0fbcb | ||
|
|
9816a9c46a | ||
|
|
f4b8f9d65d | ||
|
|
08e8d7343c | ||
|
|
5505791b6e | ||
|
|
1862477641 | ||
|
|
ff9daa2bfd | ||
|
|
23b13e3fdc | ||
|
|
f93f2674df | ||
|
|
1d7d34b7c1 | ||
|
|
9837a7af71 | ||
|
|
d3ef4094c6 | ||
|
|
bf86291016 | ||
|
|
37ace8b34c | ||
|
|
e170790e4c | ||
|
|
c0888ea2a4 | ||
|
|
0fe1aa983c | ||
|
|
180eb1f59d | ||
|
|
1ead18b53c | ||
|
|
9ca61dd60d | ||
|
|
52a0635810 | ||
|
|
2337b4cce2 | ||
|
|
6f51b90178 | ||
|
|
07db5b1fbd | ||
|
|
162e18142c | ||
|
|
6110abebb4 | ||
|
|
05e9082831 | ||
|
|
76a634cf9a | ||
|
|
d4797617a6 | ||
|
|
be67395f13 | ||
|
|
793c673414 | ||
|
|
20e17861e1 | ||
|
|
deb7375d6b | ||
|
|
2a76c279eb | ||
|
|
2767260505 | ||
|
|
bba4a8eeba | ||
|
|
5d1ba3fd87 | ||
|
|
c9786adc77 | ||
|
|
407051287d | ||
|
|
3d178dd6bc | ||
|
|
8ddedc60ac | ||
|
|
ddb8031e55 | ||
|
|
99db9a0b77 | ||
|
|
4126bc2234 | ||
|
|
d4f1ec095e | ||
|
|
bbf3ce3948 | ||
|
|
46cf24c193 | ||
|
|
fb5b8356c7 | ||
|
|
1333bd040a | ||
|
|
013c07ef59 | ||
|
|
29fd59a321 | ||
|
|
3bc152c341 | ||
|
|
ea274f8f7d | ||
|
|
7ac53e56d7 | ||
|
|
7b1c12e952 | ||
|
|
17dafe6530 | ||
|
|
ef331363f7 | ||
|
|
4f10864c2c | ||
|
|
4b5ceb0e0a | ||
|
|
4fa50161fd | ||
|
|
3d27e07ccb | ||
|
|
ca1ca71a62 | ||
|
|
bdd6c7b804 | ||
|
|
04b8082f04 | ||
|
|
82ab1376cc | ||
|
|
2c27a67cc3 | ||
|
|
3451b19b96 | ||
|
|
ae7c1ea1c4 | ||
|
|
9c76b955e2 | ||
|
|
6c4bfe2798 | ||
|
|
98189cd14a | ||
|
|
823ce262bb | ||
|
|
34358400e8 | ||
|
|
da9d069a9a | ||
|
|
58a03678e4 | ||
|
|
5ea68435ed | ||
|
|
58ca85ce90 | ||
|
|
f5c281cf86 | ||
|
|
c72bcea7d9 | ||
|
|
49cd160e76 | ||
|
|
5ca93a25ca | ||
|
|
2ea207a63a | ||
|
|
55554fc781 | ||
|
|
8e78c10304 | ||
|
|
0eb74b46ba | ||
|
|
f21dde8f7d | ||
|
|
e47c00f0b6 | ||
|
|
1fc6707bb3 | ||
|
|
44c5d66afa | ||
|
|
04edb49c9f | ||
|
|
7483d351c3 | ||
|
|
e3eb542e59 | ||
|
|
428b199dd3 | ||
|
|
3c4a6efeea | ||
|
|
dae7024043 | ||
|
|
64e5c26e8c | ||
|
|
84ae60edd6 | ||
|
|
d4c9989069 | ||
|
|
6cda413913 | ||
|
|
a11eaf031d | ||
|
|
3c701359fd | ||
|
|
85ba9fe0ab | ||
|
|
71077f4c8e | ||
|
|
4c7ff53d78 | ||
|
|
83a3810301 | ||
|
|
b31691dd9c | ||
|
|
0131cae0d7 | ||
|
|
f37b19eab6 | ||
|
|
7c1db5e0e0 | ||
|
|
c6309bf14d | ||
|
|
2c495ab8fd | ||
|
|
ced1b7084d | ||
|
|
c10a2ae1c8 | ||
|
|
3dd09d9fc7 | ||
|
|
46f05dbfa0 | ||
|
|
b0c871eff7 | ||
|
|
bd83fa3417 | ||
|
|
c0b63a655e | ||
|
|
cc21aaede1 | ||
|
|
0c71c80a25 | ||
|
|
823d1d7812 | ||
|
|
a274b5a261 | ||
|
|
49e42757bc | ||
|
|
0964f442be | ||
|
|
73ceeb1b38 | ||
|
|
8737d1eb0a | ||
|
|
814365d7de | ||
|
|
8c1a976b5b | ||
|
|
66db81071e | ||
|
|
6df6e612bc | ||
|
|
a123588e32 | ||
|
|
3fee2a781f | ||
|
|
55839f0913 | ||
|
|
f68131fc13 | ||
|
|
7b9081a125 | ||
|
|
54d3a35b38 | ||
|
|
cc91225b01 | ||
|
|
af8db9457f | ||
|
|
5860cf3a22 | ||
|
|
9d6cc405a9 | ||
|
|
1c37591bab | ||
|
|
c7c69ea5a7 | ||
|
|
cdaaa34569 | ||
|
|
17637146be | ||
|
|
b4033071a1 | ||
|
|
0c66a8f3b7 | ||
|
|
ba42b74578 | ||
|
|
ef7bae98d4 | ||
|
|
d2a1f5e48b | ||
|
|
69b717f517 | ||
|
|
6d0461eaef | ||
|
|
be72a594d7 | ||
|
|
8cc4f58076 | ||
|
|
3f07739756 |
@@ -29,4 +29,5 @@ settings.json
|
||||
.agent
|
||||
|
||||
apps/**/tsconfig.tsbuildinfo
|
||||
packages/**/tsconfig.tsbuildinfo
|
||||
logs/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { AcntgCodeManagementPage as default } from "@/pages/acntg-code-management";
|
||||
@@ -1 +0,0 @@
|
||||
export { CnstpCodeManagementPage as default } from "@/pages/cnstp-code-management";
|
||||
@@ -0,0 +1 @@
|
||||
export { CodeManagementPage as default } from "@/pages/code-management";
|
||||
@@ -0,0 +1,10 @@
|
||||
import { FnrsEditPage } from "@/pages/fnrs-cnsltn";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { id } = await params;
|
||||
return <FnrsEditPage id={id} />;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { FnrsViewPage } from "@/pages/fnrs-cnsltn";
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { id } = await params;
|
||||
return <FnrsViewPage id={id} />;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { FnrsListPage } from "@/pages/fnrs-cnsltn";
|
||||
|
||||
export default FnrsListPage;
|
||||
@@ -0,0 +1,12 @@
|
||||
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} />;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { InstlEditPage } from "@/pages/instl-sncn";
|
||||
|
||||
interface InstlSncnEditRouteProps {
|
||||
params: Promise<{ id: string }>;
|
||||
}
|
||||
|
||||
export default async function Page({ params }: InstlSncnEditRouteProps) {
|
||||
const { id } = await params;
|
||||
return <InstlEditPage id={id} />;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { CnfmModifyPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default async function InstlSncnModifyPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = await params;
|
||||
return <CnfmModifyPage id={id} />;
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
import { CnfmViewPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default async function InstlSncnViewPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ id: string }>;
|
||||
}) {
|
||||
const { id } = await params;
|
||||
return <CnfmViewPage id={id} />;
|
||||
}
|
||||
import { InstlViewPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default async function InstlSncnDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
return <InstlViewPage id={id} />;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { CnfmListPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default CnfmListPage;
|
||||
import { InstlListPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default InstlListPage;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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,5 +0,0 @@
|
||||
import { CnfmRegistPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default function Page(props: any) {
|
||||
return <CnfmRegistPage {...props} />;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ACCESS_TOKEN,
|
||||
AUTH_USER_ID,
|
||||
CLAIM_NAME,
|
||||
COOKIE_DOMAIN,
|
||||
REFRESH_TOKEN,
|
||||
SERVER_API_URL,
|
||||
SITE_ID,
|
||||
@@ -20,6 +21,10 @@ async function handler(
|
||||
"Content-Type": "application/json",
|
||||
"X-Site-Id": SITE_ID,
|
||||
};
|
||||
const deviceId = request.headers.get("X-Device-Id");
|
||||
if (deviceId) {
|
||||
headers["X-Device-Id"] = deviceId;
|
||||
}
|
||||
let body = {};
|
||||
const isRefreshHandler = params.url.includes("refresh");
|
||||
|
||||
@@ -39,27 +44,13 @@ async function handler(
|
||||
}
|
||||
|
||||
const targetUrl = `${SERVER_API_URL}/${params.url.join("/")}`;
|
||||
console.log(`[Proxy API] ${method} ${params.url.join("/")} -> ${targetUrl}`);
|
||||
console.log(`[Proxy API] Headers:`, headers);
|
||||
if (!isRefreshHandler) {
|
||||
console.log(`[Proxy API] Body:`, body);
|
||||
}
|
||||
|
||||
const result = await fetch(targetUrl, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
console.log(`[Proxy API] Response Status: ${result.status}`);
|
||||
console.log(`[Proxy API] Response Headers:`, {
|
||||
[ACCESS_TOKEN]: result.headers.get(ACCESS_TOKEN),
|
||||
[REFRESH_TOKEN]: result.headers.get(REFRESH_TOKEN) ? '***hidden***' : undefined,
|
||||
[AUTH_USER_ID]: result.headers.get(AUTH_USER_ID),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
console.error(`[Proxy API] Request failed with status ${result.status}`);
|
||||
return NextResponse.json({ message: "Invalid Credentials" }, { status: 401 });
|
||||
}
|
||||
|
||||
@@ -68,24 +59,16 @@ async function handler(
|
||||
const userId = result.headers.get(AUTH_USER_ID);
|
||||
|
||||
if (accessToken && userId) {
|
||||
console.log(`[Proxy API] Login successful for user: ${userId}`);
|
||||
const response = NextResponse.json({
|
||||
[ACCESS_TOKEN]: accessToken,
|
||||
[AUTH_USER_ID]: userId,
|
||||
});
|
||||
|
||||
const forwardedHost = request.headers.get("x-forwarded-host");
|
||||
const host = request.headers.get("host");
|
||||
const actualHost = forwardedHost || host || new URL(request.url).hostname.toString();
|
||||
|
||||
// 운영 환경에서 domain 설정
|
||||
const domain = actualHost.includes("hasuon.or.kr") ? ".hasuon.or.kr" : actualHost;
|
||||
|
||||
if (refreshToken) {
|
||||
cookieStore.set(REFRESH_TOKEN, refreshToken, {
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
domain: domain,
|
||||
domain: COOKIE_DOMAIN,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
# 재원협의(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` |
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ["@common/assets"],
|
||||
};
|
||||
|
||||
@@ -20,16 +20,19 @@
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-query": "^5.75.5",
|
||||
"@tanstack/react-query-devtools": "^5.75.5",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"axios": "^1.8.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.474.0",
|
||||
"next": "15.5.18",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"tus-js-client": "^4.3.1",
|
||||
"uuid": "^14.0.1",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -7,6 +7,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useMenuStore } from "@/shared/lib";
|
||||
import { PageLayout } from "@/shared/ui/page-layout";
|
||||
import { authService } from "@/entities/auth";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { BaseLayout } from "@common/components";
|
||||
|
||||
@@ -32,6 +33,7 @@ export function BaseLayoutWrapper({ children }: BaseLayoutWrapperProps) {
|
||||
return (
|
||||
<BaseLayout
|
||||
queryClient={queryClient}
|
||||
authService={authService}
|
||||
currentPath={pathname ?? ""}
|
||||
handleMenuClick={handleMenuClick}
|
||||
>
|
||||
|
||||
@@ -27,7 +27,9 @@ export function Providers({ children }: ProvidersProps) {
|
||||
</AuthWrapper>
|
||||
</ConfirmProvider>
|
||||
</StoreProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
<div style={{ fontSize: "1.4rem" }}>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</div>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type AcntgSeMngTreeDto,
|
||||
} from "../model/acntg-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_COMMON_SERVICE.ACNTG_SE;
|
||||
const BASE = ENDPOINT.AS_SERVICE.ACNTG_SE;
|
||||
|
||||
async function findAll(params: AcntgSeMngSearchDto): Promise<PageResponse<AcntgSeMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<AcntgSeMngResponseDto>>(BASE, { params });
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
|
||||
import { acntgSeMngService } from "./acntg-service";
|
||||
|
||||
const acntgSeMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.ACNTG_SE] as const;
|
||||
const acntgSeMngBaseKey = [ENDPOINT.AS_SERVICE.ACNTG_SE] as const;
|
||||
|
||||
export function useAcntgSeMngPage(params: AcntgSeMngSearchDto) {
|
||||
return useQuery({
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface AcntgSeMngResponseDto {
|
||||
cdLvNo: number;
|
||||
useYn: string;
|
||||
expln?: string;
|
||||
sortSeq?: number;
|
||||
rgtr?: string;
|
||||
rgtrNm?: string;
|
||||
mdfr?: string;
|
||||
@@ -39,6 +40,7 @@ export interface AcntgSeMngSaveRequestDto {
|
||||
cdLvNo: number;
|
||||
useYn?: string;
|
||||
expln?: string;
|
||||
sortSeq?: number;
|
||||
}
|
||||
|
||||
/** PUT /{acntgSeCd} 요청 (mode는 서비스에서 U로 설정) */
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
CmCdAdstDstrComboResponseDto,
|
||||
CmCdAdstDstrService,
|
||||
} from "../model/adstdstr-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.ADSTDSTR;
|
||||
|
||||
async function findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]> {
|
||||
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/sido`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]> {
|
||||
const response = await client.get<CmCdAdstDstrComboResponseDto[]>(`${BASE}/gugun`, {
|
||||
params: { sido },
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const cmCdAdstDstrService = {
|
||||
findSidoCombo,
|
||||
findGugunCombo,
|
||||
} satisfies CmCdAdstDstrService;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import { cmCdAdstDstrService } from "./adstdstr-service";
|
||||
|
||||
const cmCdAdstDstrBaseKey = [ENDPOINT.AS_SERVICE.ADSTDSTR] as const;
|
||||
|
||||
export function useFindSidoCombo() {
|
||||
return useQuery({
|
||||
queryKey: [...cmCdAdstDstrBaseKey, "findSidoCombo"],
|
||||
queryFn: () => cmCdAdstDstrService.findSidoCombo(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useFindGugunCombo(sido: string) {
|
||||
return useQuery({
|
||||
queryKey: [...cmCdAdstDstrBaseKey, "findGugunCombo", sido],
|
||||
queryFn: () => cmCdAdstDstrService.findGugunCombo(sido),
|
||||
enabled: Boolean(sido),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export { cmCdAdstDstrService } from "./api/adstdstr-service";
|
||||
export { useFindGugunCombo, useFindSidoCombo } from "./api/queries";
|
||||
export type {
|
||||
CmCdAdstDstrComboResponseDto,
|
||||
CmCdAdstDstrService,
|
||||
} from "./model/adstdstr-interface";
|
||||
@@ -0,0 +1,11 @@
|
||||
/** 백엔드 CmCdAdstDstrComboResponseDto */
|
||||
export interface CmCdAdstDstrComboResponseDto {
|
||||
adstDstrCd: string;
|
||||
adstDstrNm: string;
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface CmCdAdstDstrService {
|
||||
findSidoCombo(): Promise<CmCdAdstDstrComboResponseDto[]>;
|
||||
findGugunCombo(sido: string): Promise<CmCdAdstDstrComboResponseDto[]>;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type { AttachService } from "../model/attach-interface";
|
||||
|
||||
async function deleteAttachmentByAtflId(atflId: string): Promise<void> {
|
||||
if (!atflId) return;
|
||||
await client.delete(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflId}`);
|
||||
}
|
||||
|
||||
async function downloadAttachmentByAtflCd(atflCd: string): Promise<AxiosResponse> {
|
||||
return await client.get<Blob>(`${ENDPOINT.ATTACH_SERVICE.DOWNLOAD}/${atflCd}`, {
|
||||
responseType: "blob",
|
||||
});
|
||||
}
|
||||
|
||||
export const attachService = {
|
||||
deleteAttachmentByAtflId,
|
||||
downloadAttachmentByAtflCd,
|
||||
} satisfies AttachService;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import { parseFilenameFromResponse } from "../lib/parse-filename-from-response";
|
||||
|
||||
import { attachService } from "./attach-service";
|
||||
|
||||
const attachBaseKey = [ENDPOINT.ATTACH_SERVICE.ATTACHMENTS] as const;
|
||||
const attachDownloadBaseKey = [ENDPOINT.ATTACH_SERVICE.DOWNLOAD] as const;
|
||||
|
||||
export function useDeleteAttachment() {
|
||||
return useMutation({
|
||||
mutationKey: [...attachBaseKey, "delete"],
|
||||
mutationFn: (atflId: string) => attachService.deleteAttachmentByAtflId(atflId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDownloadAttachmentByAtflCd() {
|
||||
return useMutation({
|
||||
mutationKey: [...attachDownloadBaseKey, "download"],
|
||||
mutationFn: async (atflCd: string) => {
|
||||
const response = await attachService.downloadAttachmentByAtflCd(atflCd);
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { attachService } from "./api/attach-service";
|
||||
export { useDeleteAttachment, useDownloadAttachmentByAtflCd } from "./api/queries";
|
||||
export type { Attach, AttachService } from "./model/attach-interface";
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export function parseFilenameFromResponse(response: AxiosResponse): string {
|
||||
const contentDisposition = response.headers["content-disposition"];
|
||||
if (!contentDisposition) return "download";
|
||||
|
||||
const filenameMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/);
|
||||
if (!filenameMatch?.[1]) return "download";
|
||||
|
||||
return decodeURIComponent(filenameMatch[1]);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { AxiosResponse } from "axios";
|
||||
|
||||
/** 백엔드 AtflMngResponseDto */
|
||||
export interface Attach {
|
||||
atflCd: string;
|
||||
atflSn: number;
|
||||
atflId: string;
|
||||
physFileNm: string;
|
||||
orgnlFileNm: string;
|
||||
atflFlieSz: number;
|
||||
atflType: string;
|
||||
dwnldNocs: number;
|
||||
delYn: string;
|
||||
lnkgDmnId: string;
|
||||
lnkgDmnNm: string;
|
||||
regDt: string;
|
||||
}
|
||||
|
||||
export interface AttachService {
|
||||
deleteAttachmentByAtflId: (atflId: string) => Promise<void>;
|
||||
downloadAttachmentByAtflCd: (atflCd: string) => Promise<AxiosResponse>;
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import { ACCESS_TOKEN, AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config";
|
||||
import { getRemainingMs } from "@common/auth";
|
||||
import { type AuthPayload, type AuthService, getRemainingMs } from "@common/auth";
|
||||
|
||||
import { clearAuthToken, setToken, setupRefreshTimer } from "../lib/token-manager";
|
||||
import type { AuthPayload, AuthService } from "../model/auth-interface";
|
||||
import { clearDeviceId, getOrCreateDeviceId } from "../lib/device-id";
|
||||
import { clearRefreshTimer, setToken, setupRefreshTimer, tokenManager } from "../lib/token-manager";
|
||||
|
||||
async function login(payload: AuthPayload): Promise<void> {
|
||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Device-Id": getOrCreateDeviceId(),
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
@@ -34,6 +37,9 @@ async function silentRefresh(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"X-Device-Id": getOrCreateDeviceId(),
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
@@ -58,7 +64,7 @@ async function silentRefresh(): Promise<void> {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[silentRefresh] Failed at ${new Date().toISOString()}`, error);
|
||||
clearAuthToken();
|
||||
tokenManager.clearAuthToken();
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent("auth:logout"));
|
||||
}
|
||||
@@ -66,7 +72,9 @@ async function silentRefresh(): Promise<void> {
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
clearAuthToken();
|
||||
tokenManager.clearAuthToken();
|
||||
clearDeviceId();
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.dispatchEvent(new CustomEvent("auth:logout"));
|
||||
}
|
||||
@@ -75,5 +83,6 @@ async function logout(): Promise<void> {
|
||||
export const authService: AuthService = {
|
||||
login,
|
||||
silentRefresh,
|
||||
declineSessionExtend: clearRefreshTimer,
|
||||
logout,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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,7 +8,7 @@ export const {
|
||||
setToken,
|
||||
removeToken,
|
||||
setupRefreshTimer,
|
||||
clearAuthToken,
|
||||
clearRefreshTimer,
|
||||
getAccessToken,
|
||||
getRefreshToken,
|
||||
getUserId,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface AuthPayload {
|
||||
userId: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthService {
|
||||
login(payload: AuthPayload): Promise<void>;
|
||||
silentRefresh(): Promise<void>;
|
||||
logout(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import { buildBnsSearchDto } from "../lib/build-bns-search-dto";
|
||||
import type {
|
||||
BnsDetailResponseWrapperDto,
|
||||
BnsIfService,
|
||||
BnsListResponseWrapperDto,
|
||||
BnsSearchDto,
|
||||
EmCodeListResponseDto,
|
||||
} from "../model/bns-if-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.BNS_IF;
|
||||
|
||||
async function getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto> {
|
||||
const response = await client.get<BnsListResponseWrapperDto>(`${BASE}/bns`, {
|
||||
params: buildBnsSearchDto(params),
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function getBnsDetail(
|
||||
gbn: string,
|
||||
yr: string,
|
||||
seqNo: string,
|
||||
): Promise<BnsDetailResponseWrapperDto> {
|
||||
const response = await client.get<BnsDetailResponseWrapperDto>(`${BASE}/bns/detail`, {
|
||||
params: { gbn, yr, seqNo },
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function getEmCodeList(
|
||||
dpmCd?: string,
|
||||
lclgvrnmntCd?: string,
|
||||
): Promise<EmCodeListResponseDto> {
|
||||
const response = await client.get<EmCodeListResponseDto>(`${BASE}/emCodeList`, {
|
||||
params: { dpmCd, lclgvrnmntCd },
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const bnsIfService = {
|
||||
getBnsList,
|
||||
getBnsDetail,
|
||||
getEmCodeList,
|
||||
} satisfies BnsIfService;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type { BnsSearchDto } from "../model/bns-if-interface";
|
||||
|
||||
import { bnsIfService } from "./bns-if-service";
|
||||
|
||||
const bnsIfBaseKey = [ENDPOINT.AS_SERVICE.BNS_IF] as const;
|
||||
|
||||
export function useBnsList(params: BnsSearchDto, options?: { enabled?: boolean }) {
|
||||
return useQuery({
|
||||
queryKey: [...bnsIfBaseKey, "getBnsList", params],
|
||||
queryFn: () => bnsIfService.getBnsList(params),
|
||||
enabled: options?.enabled,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
}
|
||||
|
||||
export function useBnsDetail(gbn: string, yr: string, seqNo: string) {
|
||||
return useQuery({
|
||||
queryKey: [...bnsIfBaseKey, "getBnsDetail", gbn, yr, seqNo],
|
||||
queryFn: () => bnsIfService.getBnsDetail(gbn, yr, seqNo),
|
||||
enabled: Boolean(gbn && yr && seqNo),
|
||||
});
|
||||
}
|
||||
|
||||
export function useEmCodeList(dpmCd?: string, lclgvrnmntCd?: string) {
|
||||
return useQuery({
|
||||
queryKey: [...bnsIfBaseKey, "getEmCodeList", dpmCd, lclgvrnmntCd],
|
||||
queryFn: () => bnsIfService.getEmCodeList(dpmCd, lclgvrnmntCd),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export { bnsIfService } from "./api/bns-if-service";
|
||||
export { useBnsDetail, useBnsList, useEmCodeList } from "./api/queries";
|
||||
export { buildBnsSearchDto } from "./lib/build-bns-search-dto";
|
||||
export type {
|
||||
BnsDetailResponseDto,
|
||||
BnsDetailResponseWrapperDto,
|
||||
BnsIfService,
|
||||
BnsListResponseDto,
|
||||
BnsListResponseWrapperDto,
|
||||
BnsSearchDto,
|
||||
EmCodeItemDto,
|
||||
EmCodeListResponseDto,
|
||||
} from "./model/bns-if-interface";
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { BnsSearchDto } from "../model/bns-if-interface";
|
||||
|
||||
export function buildBnsSearchDto(input: {
|
||||
cPage: number;
|
||||
searchStartYear?: string;
|
||||
searchSido?: string;
|
||||
searchGugun?: string;
|
||||
}): BnsSearchDto {
|
||||
const dto: BnsSearchDto = { cPage: input.cPage };
|
||||
|
||||
const year = input.searchStartYear?.trim();
|
||||
const sido = input.searchSido?.trim();
|
||||
const gugun = input.searchGugun?.trim();
|
||||
|
||||
if (year) dto.searchStartYear = year;
|
||||
if (sido) dto.searchSido = sido;
|
||||
if (gugun) dto.searchGugun = gugun;
|
||||
|
||||
return dto;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/** 백엔드 BnsSearchDto */
|
||||
export interface BnsSearchDto {
|
||||
searchStartYear?: string;
|
||||
cPage: number;
|
||||
searchSido?: string;
|
||||
searchGugun?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 BnsListResponseWrapperDto */
|
||||
export interface BnsListResponseWrapperDto {
|
||||
list: BnsListResponseDto[];
|
||||
cnt: number;
|
||||
}
|
||||
|
||||
/** 백엔드 BnsListResponseDto */
|
||||
export interface BnsListResponseDto {
|
||||
cPage: number;
|
||||
gbn: number;
|
||||
chk: number;
|
||||
seqNo: string;
|
||||
rgnNm: string;
|
||||
sido: string;
|
||||
gugun: string;
|
||||
bzCd: string;
|
||||
bzNm: string;
|
||||
yr: string;
|
||||
strtYr: string;
|
||||
edYr: string;
|
||||
totBzamt: string;
|
||||
}
|
||||
|
||||
/** 백엔드 BnsDetailResponseWrapperDto */
|
||||
export interface BnsDetailResponseWrapperDto {
|
||||
saup: unknown;
|
||||
detail: BnsDetailResponseDto;
|
||||
}
|
||||
|
||||
/** 백엔드 BnsDetailResponseDto */
|
||||
export interface BnsDetailResponseDto {
|
||||
cPage: number;
|
||||
seqNo: string;
|
||||
confBzCd: string;
|
||||
chngDgr: string;
|
||||
yr: string;
|
||||
sido: string;
|
||||
gugun: string;
|
||||
rvbsnNm: string;
|
||||
lgbsnNm: string;
|
||||
mdmbsnNm: string;
|
||||
lclgvrnmntNm: string;
|
||||
accKdNm: string;
|
||||
bzKdNm: string;
|
||||
untBzNm: string;
|
||||
dtlBzNm: string;
|
||||
mevBzNm: string;
|
||||
ddtlBzNm: string;
|
||||
strtYr: string;
|
||||
edYr: string;
|
||||
aidRt: string;
|
||||
bzStatus: string;
|
||||
bzCd: string;
|
||||
bzNm: string;
|
||||
bzLoc: string;
|
||||
efflWqltStndrdNm: string;
|
||||
goalYear: string;
|
||||
bssProm: string;
|
||||
uppPlan: string;
|
||||
techInsp: string;
|
||||
bssCont: string;
|
||||
rmk: string;
|
||||
inflBscFctNm: string;
|
||||
inflBscFctCd: string;
|
||||
trtMtd: string;
|
||||
swrExtns: string;
|
||||
swrChng: string;
|
||||
swrRnvt: string;
|
||||
totFamNum: string;
|
||||
pumpCnt: string;
|
||||
rvWbsn: string;
|
||||
rvText: string;
|
||||
imprvExpctFlw: string;
|
||||
imprvExpctWqlt: string;
|
||||
trtplntPlEfflWqlt: string;
|
||||
wqltAvg: string;
|
||||
wqltHgh: string;
|
||||
wqltLow: string;
|
||||
stdCost: string;
|
||||
bzEtc: string;
|
||||
bscFctCd: string;
|
||||
bscFctNm: string;
|
||||
fctCpc: string;
|
||||
fctCpcAfr: string;
|
||||
trtDstrArea: string;
|
||||
trtDstrAreaAfr: string;
|
||||
swrTrtPopl: string;
|
||||
swrTrtPoplAfr: string;
|
||||
adstPopl: string;
|
||||
}
|
||||
|
||||
/** 백엔드 EmCodeItemDto */
|
||||
export interface EmCodeItemDto {
|
||||
code: string;
|
||||
data: string;
|
||||
upCode: string;
|
||||
}
|
||||
|
||||
/** 백엔드 EmCodeListResponseDto */
|
||||
export interface EmCodeListResponseDto {
|
||||
rvbsnList: EmCodeItemDto[];
|
||||
lgbsnList: EmCodeItemDto[];
|
||||
mdmbsnList: EmCodeItemDto[];
|
||||
accList: EmCodeItemDto[];
|
||||
bzList: EmCodeItemDto[];
|
||||
untList: EmCodeItemDto[];
|
||||
dtlList: EmCodeItemDto[];
|
||||
mevList: EmCodeItemDto[];
|
||||
ddtlList: EmCodeItemDto[];
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface BnsIfService {
|
||||
getBnsList(params: BnsSearchDto): Promise<BnsListResponseWrapperDto>;
|
||||
getBnsDetail(gbn: string, yr: string, seqNo: string): Promise<BnsDetailResponseWrapperDto>;
|
||||
getEmCodeList(dpmCd?: string, lclgvrnmntCd?: string): Promise<EmCodeListResponseDto>;
|
||||
}
|
||||
@@ -11,33 +11,25 @@ import type {
|
||||
CklstArtclUpdateRequest,
|
||||
} from "../model/cklst-artcl-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.CKLST_ARTCL;
|
||||
|
||||
async function findPage(params: CklstArtclSearchParams): Promise<PageResponse<CklstArtcl>> {
|
||||
const response = await client.get<PageResponse<CklstArtcl>>(
|
||||
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL,
|
||||
{ params },
|
||||
);
|
||||
const response = await client.get<PageResponse<CklstArtcl>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findByCklstSn(cklstSn: number): Promise<CklstArtcl[]> {
|
||||
const response = await client.get<CklstArtcl[]>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/by-cklst/${cklstSn}`,
|
||||
);
|
||||
const response = await client.get<CklstArtcl[]>(`${BASE}/by-cklst/${cklstSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findById(cklstArtclSn: number, cklstSn: number): Promise<CklstArtcl> {
|
||||
const response = await client.get<CklstArtcl>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/${cklstArtclSn}/${cklstSn}`,
|
||||
);
|
||||
const response = await client.get<CklstArtcl>(`${BASE}/${cklstArtclSn}/${cklstSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function create(body: CklstArtclSaveRequest): Promise<CklstArtclId> {
|
||||
const response = await client.post<CklstArtclId>(
|
||||
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL,
|
||||
body,
|
||||
);
|
||||
const response = await client.post<CklstArtclId>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -46,17 +38,12 @@ async function update(
|
||||
cklstSn: number,
|
||||
body: CklstArtclUpdateRequest,
|
||||
): Promise<CklstArtclId> {
|
||||
const response = await client.put<CklstArtclId>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/${cklstArtclSn}/${cklstSn}`,
|
||||
body,
|
||||
);
|
||||
const response = await client.put<CklstArtclId>(`${BASE}/${cklstArtclSn}/${cklstSn}`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(cklstArtclSn: number, cklstSn: number): Promise<CklstArtclId> {
|
||||
const response = await client.delete<CklstArtclId>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL}/${cklstArtclSn}/${cklstSn}`,
|
||||
);
|
||||
const response = await client.delete<CklstArtclId>(`${BASE}/${cklstArtclSn}/${cklstSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,40 +11,39 @@ import type {
|
||||
CklstTreeSearchParams,
|
||||
} from "../model/cklst-mng-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.CKLST;
|
||||
|
||||
async function findPage(params: CklstMngSearchParams): Promise<PageResponse<CklstMng>> {
|
||||
const response = await client.get<PageResponse<CklstMng>>(ENDPOINT.AS_CHECKLIST_SERVICE.CKLST, {
|
||||
const response = await client.get<PageResponse<CklstMng>>(BASE, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findById(cklstSn: number): Promise<CklstMng> {
|
||||
const response = await client.get<CklstMng>(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/${cklstSn}`);
|
||||
const response = await client.get<CklstMng>(`${BASE}/${cklstSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findTree(params: CklstTreeSearchParams): Promise<CklstTreeNode[]> {
|
||||
const response = await client.get<CklstTreeNode[]>(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/list/tree`, {
|
||||
const response = await client.get<CklstTreeNode[]>(`${BASE}/list/tree`, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function create(body: CklstMngSaveRequest): Promise<number> {
|
||||
const response = await client.post<number>(ENDPOINT.AS_CHECKLIST_SERVICE.CKLST, body);
|
||||
const response = await client.post<number>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(cklstSn: number, body: CklstMngSaveRequest): Promise<number> {
|
||||
const response = await client.put<number>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/${cklstSn}`,
|
||||
body,
|
||||
);
|
||||
const response = await client.put<number>(`${BASE}/${cklstSn}`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(cklstSn: number): Promise<number> {
|
||||
const response = await client.delete<number>(`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST}/${cklstSn}`);
|
||||
const response = await client.delete<number>(`${BASE}/${cklstSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,18 +11,15 @@ import type {
|
||||
CklstRvwUpdateRequest,
|
||||
} from "../model/cklst-rvw-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.CKLST_RVW;
|
||||
|
||||
async function findPage(params: CklstRvwSearchParams): Promise<PageResponse<CklstRvw>> {
|
||||
const response = await client.get<PageResponse<CklstRvw>>(
|
||||
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW,
|
||||
{ params },
|
||||
);
|
||||
const response = await client.get<PageResponse<CklstRvw>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findByArtcl(cklstSn: number, cklstArtclSn: number): Promise<CklstRvw[]> {
|
||||
const response = await client.get<CklstRvw[]>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/by-artcl/${cklstSn}/${cklstArtclSn}`,
|
||||
);
|
||||
const response = await client.get<CklstRvw[]>(`${BASE}/by-artcl/${cklstSn}/${cklstArtclSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -31,17 +28,12 @@ async function findById(
|
||||
cklstArtclSn: number,
|
||||
cklstSn: number,
|
||||
): Promise<CklstRvw> {
|
||||
const response = await client.get<CklstRvw>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
|
||||
);
|
||||
const response = await client.get<CklstRvw>(`${BASE}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function create(body: CklstRvwSaveRequest): Promise<CklstRvwId> {
|
||||
const response = await client.post<CklstRvwId>(
|
||||
ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW,
|
||||
body,
|
||||
);
|
||||
const response = await client.post<CklstRvwId>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -52,7 +44,7 @@ async function update(
|
||||
body: CklstRvwUpdateRequest,
|
||||
): Promise<CklstRvwId> {
|
||||
const response = await client.put<CklstRvwId>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
|
||||
`${BASE}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
@@ -64,7 +56,7 @@ async function remove(
|
||||
cklstSn: number,
|
||||
): Promise<CklstRvwId> {
|
||||
const response = await client.delete<CklstRvwId>(
|
||||
`${ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
|
||||
`${BASE}/${cklstRvwSn}/${cklstArtclSn}/${cklstSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ import { cklstArtclService } from "./cklst-artcl-service";
|
||||
import { cklstMngService } from "./cklst-mng-service";
|
||||
import { cklstRvwService } from "./cklst-rvw-service";
|
||||
|
||||
const cklstMngBaseKey = [ENDPOINT.AS_CHECKLIST_SERVICE.CKLST] as const;
|
||||
const cklstArtclBaseKey = [ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_ARTCL] as const;
|
||||
const cklstRvwBaseKey = [ENDPOINT.AS_CHECKLIST_SERVICE.CKLST_RVW] as const;
|
||||
const cklstMngBaseKey = [ENDPOINT.AS_SERVICE.CKLST] as const;
|
||||
const cklstArtclBaseKey = [ENDPOINT.AS_SERVICE.CKLST_ARTCL] as const;
|
||||
const cklstRvwBaseKey = [ENDPOINT.AS_SERVICE.CKLST_RVW] as const;
|
||||
|
||||
export function useCklstMngPage(params: CklstMngSearchParams) {
|
||||
return useQuery({
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
type CnstpSeMngTreeDto,
|
||||
} from "../model/cnstp-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_COMMON_SERVICE.CNSTP_SE;
|
||||
const BASE = ENDPOINT.AS_SERVICE.CNSTP_SE;
|
||||
|
||||
async function findAll(params: CnstpSeMngSearchDto): Promise<PageResponse<CnstpSeMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<CnstpSeMngResponseDto>>(BASE, { params });
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
|
||||
import { cnstpSeMngService } from "./cnstp-service";
|
||||
|
||||
const cnstpSeMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.CNSTP_SE] as const;
|
||||
const cnstpSeMngBaseKey = [ENDPOINT.AS_SERVICE.CNSTP_SE] as const;
|
||||
|
||||
export function useCnstpSeMngPage(params: CnstpSeMngSearchDto) {
|
||||
return useQuery({
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface CnstpSeMngResponseDto {
|
||||
cdLvNo: number;
|
||||
useYn: string;
|
||||
expln?: string;
|
||||
sortSeq?: number;
|
||||
rgtr?: string;
|
||||
rgtrNm?: string;
|
||||
mdfr?: string;
|
||||
@@ -39,6 +40,7 @@ export interface CnstpSeMngSaveRequestDto {
|
||||
cdLvNo?: number;
|
||||
useYn?: string;
|
||||
expln?: string;
|
||||
sortSeq?: number;
|
||||
}
|
||||
|
||||
/** PUT /{cnstpSeCd} 요청 (mode는 서비스에서 U로 설정) */
|
||||
|
||||
@@ -1,17 +1,67 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
ElaprDmndResponse,
|
||||
ElaprDmndSaveRequest,
|
||||
ElaprDmndResponseDto,
|
||||
ElaprDmndSaveRequestDto,
|
||||
ElaprDmndSearchDto,
|
||||
ElaprDmndService,
|
||||
ElaprDocPrgrsResponseDto,
|
||||
} from "../model/elapr-dmnd-interface";
|
||||
|
||||
async function create(body: ElaprDmndSaveRequest): Promise<ElaprDmndResponse> {
|
||||
const response = await client.post<ElaprDmndResponse>(ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, body);
|
||||
const BASE = ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND;
|
||||
|
||||
async function findPage(params: ElaprDmndSearchDto): Promise<PageResponse<ElaprDmndResponseDto>> {
|
||||
const response = await client.get<PageResponse<ElaprDmndResponseDto>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const ElaprDmndApi: ElaprDmndService = {
|
||||
create,
|
||||
};
|
||||
async function findById(elaprDmndId: string): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.get<ElaprDmndResponseDto>(`${BASE}/${elaprDmndId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function save(body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.post<ElaprDmndResponseDto>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(
|
||||
elaprDmndId: string,
|
||||
body: ElaprDmndSaveRequestDto,
|
||||
): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.put<ElaprDmndResponseDto>(`${BASE}/${elaprDmndId}`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function updateStatus(
|
||||
elaprDmndId: string,
|
||||
atrzSttsComCd: string,
|
||||
): Promise<ElaprDmndResponseDto> {
|
||||
const response = await client.put<ElaprDmndResponseDto>(`${BASE}/${elaprDmndId}/status`, null, {
|
||||
params: { atrzSttsComCd },
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(elaprDmndId: string): Promise<void> {
|
||||
await client.delete(`${BASE}/${elaprDmndId}`);
|
||||
}
|
||||
|
||||
async function findDocPrgrsByDocNo(elaprDocNo: string): Promise<ElaprDocPrgrsResponseDto[]> {
|
||||
const response = await client.get<ElaprDocPrgrsResponseDto[]>(
|
||||
`${BASE}/sanc/docprgrs/${elaprDocNo}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const elaprDmndService = {
|
||||
findPage,
|
||||
findById,
|
||||
save,
|
||||
update,
|
||||
updateStatus,
|
||||
remove,
|
||||
findDocPrgrsByDocNo,
|
||||
} satisfies ElaprDmndService;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { client } from "@/shared/api";
|
||||
|
||||
export async function processElaprPrgrs(elaprPrgrsId: string, atrzPrgrsSttsComCd = "ATRZ_STTS_003"): Promise<void> {
|
||||
await client.put(`/as-elapr-service/api/v1/elapr-prgrs-stts/${elaprPrgrsId}/process`, {
|
||||
atrzPrgrsSttsComCd,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
ElaprApproverChangeRequestDto,
|
||||
ElaprApproverInsertRequestDto,
|
||||
ElaprPrgrsSttsMyDocSearchParams,
|
||||
ElaprPrgrsSttsResponseDto,
|
||||
ElaprPrgrsSttsSaveRequestDto,
|
||||
ElaprPrgrsSttsService,
|
||||
ElaprProcessRequestDto,
|
||||
} from "../model/elapr-prgrs-stts-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS;
|
||||
|
||||
async function findAllByDmndId(elaprDmndId?: string): Promise<ElaprPrgrsSttsResponseDto[]> {
|
||||
const response = await client.get<ElaprPrgrsSttsResponseDto[]>(BASE, {
|
||||
params: elaprDmndId ? { elaprDmndId } : undefined,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findById(elaprPrgrsId: string): Promise<ElaprPrgrsSttsResponseDto> {
|
||||
const response = await client.get<ElaprPrgrsSttsResponseDto>(`${BASE}/${elaprPrgrsId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function save(
|
||||
requestDtos: ElaprPrgrsSttsSaveRequestDto[],
|
||||
): Promise<ElaprPrgrsSttsResponseDto[]> {
|
||||
const response = await client.post<ElaprPrgrsSttsResponseDto[]>(BASE, requestDtos);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function process(
|
||||
elaprPrgrsId: string,
|
||||
body: ElaprProcessRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto> {
|
||||
const response = await client.put<ElaprPrgrsSttsResponseDto>(
|
||||
`${BASE}/${elaprPrgrsId}/process`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function processByDocNoAndAprvrUuid(
|
||||
elaprDocNo: string,
|
||||
aprvrUuid: string,
|
||||
body: ElaprProcessRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto> {
|
||||
const response = await client.put<ElaprPrgrsSttsResponseDto>(
|
||||
`${BASE}/doc/${elaprDocNo}/aprvr/${aprvrUuid}/process`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function deletePrgrsStts(elaprPrgrsId: string): Promise<void> {
|
||||
await client.delete(`${BASE}/${elaprPrgrsId}`);
|
||||
}
|
||||
|
||||
async function updateApprover(
|
||||
elaprPrgrsId: string,
|
||||
body: ElaprApproverChangeRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto> {
|
||||
const response = await client.put<ElaprPrgrsSttsResponseDto>(
|
||||
`${BASE}/${elaprPrgrsId}/approver`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function insertApprover(
|
||||
elaprDmndId: string,
|
||||
body: ElaprApproverInsertRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto> {
|
||||
const response = await client.post<ElaprPrgrsSttsResponseDto>(
|
||||
`${BASE}/${elaprDmndId}/approver/insert`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findMyElaperDocList(
|
||||
params: ElaprPrgrsSttsMyDocSearchParams,
|
||||
): Promise<PageResponse<ElaprPrgrsSttsResponseDto>> {
|
||||
const response = await client.get<PageResponse<ElaprPrgrsSttsResponseDto>>(`${BASE}/my/doc`, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const elaprPrgrsSttsService = {
|
||||
findAllByDmndId,
|
||||
findById,
|
||||
save,
|
||||
process,
|
||||
processByDocNoAndAprvrUuid,
|
||||
delete: deletePrgrsStts,
|
||||
updateApprover,
|
||||
insertApprover,
|
||||
findMyElaperDocList,
|
||||
} satisfies ElaprPrgrsSttsService;
|
||||
@@ -8,6 +8,14 @@ import type {
|
||||
ElaprAplnSaveRequest,
|
||||
ElaprAplnUpdateRequest,
|
||||
} from "../model/elapr-apln-interface";
|
||||
import type { ElaprDmndSaveRequestDto, ElaprDmndSearchDto } from "../model/elapr-dmnd-interface";
|
||||
import type {
|
||||
ElaprApproverChangeRequestDto,
|
||||
ElaprApproverInsertRequestDto,
|
||||
ElaprPrgrsSttsMyDocSearchParams,
|
||||
ElaprPrgrsSttsSaveRequestDto,
|
||||
ElaprProcessRequestDto,
|
||||
} from "../model/elapr-prgrs-stts-interface";
|
||||
import type {
|
||||
ElaprTmpltSaveRequest,
|
||||
ElaprTmpltSearchParams,
|
||||
@@ -15,10 +23,19 @@ import type {
|
||||
} from "../model/elapr-tmplt-interface";
|
||||
|
||||
import { elaprAplnService } from "./elapr-apln-service";
|
||||
import { elaprDmndService } from "./elapr-dmnd-service";
|
||||
import { elaprPrgrsSttsService } from "./elapr-prgrs-stts-service";
|
||||
import { elaprTmpltService } from "./elapr-tmplt-service";
|
||||
|
||||
const elaprTmpltBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_TMPLT] as const;
|
||||
const elaprAplnBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_APLN] as const;
|
||||
const elaprDmndBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND] as const;
|
||||
const elaprPrgrsSttsBaseKey = [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_PRGRS_STTS] as const;
|
||||
|
||||
function invalidateElaprPrgrsSttsQueries(queryClient: ReturnType<typeof useQueryClient>) {
|
||||
queryClient.invalidateQueries({ queryKey: elaprPrgrsSttsBaseKey });
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
}
|
||||
|
||||
export function useElaprTmpltPage(params: ElaprTmpltSearchParams) {
|
||||
return useQuery({
|
||||
@@ -50,13 +67,8 @@ export function useUpdateElaprTmplt() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprTmpltId,
|
||||
body,
|
||||
}: {
|
||||
elaprTmpltId: string;
|
||||
body: ElaprTmpltUpdateRequest;
|
||||
}) => elaprTmpltService.update(elaprTmpltId, body),
|
||||
mutationFn: ({ elaprTmpltId, body }: { elaprTmpltId: string; body: ElaprTmpltUpdateRequest }) =>
|
||||
elaprTmpltService.update(elaprTmpltId, body),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprTmpltBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -99,12 +111,8 @@ export function useSaveMultiElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
body,
|
||||
}: {
|
||||
body: ElaprAplnSaveRequest[];
|
||||
elaprTmpltId: string;
|
||||
}) => elaprAplnService.saveMulti(body),
|
||||
mutationFn: ({ body }: { body: ElaprAplnSaveRequest[]; elaprTmpltId: string }) =>
|
||||
elaprAplnService.saveMulti(body),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -118,13 +126,8 @@ export function useUpdateElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
aplnId,
|
||||
body,
|
||||
}: {
|
||||
aplnId: string;
|
||||
body: ElaprAplnUpdateRequest;
|
||||
}) => elaprAplnService.update(aplnId, body),
|
||||
mutationFn: ({ aplnId, body }: { aplnId: string; body: ElaprAplnUpdateRequest }) =>
|
||||
elaprAplnService.update(aplnId, body),
|
||||
onSuccess: (_data, { body, aplnId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -141,12 +144,8 @@ export function useDeleteElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
aplnId,
|
||||
}: {
|
||||
aplnId: string;
|
||||
elaprTmpltId: string;
|
||||
}) => elaprAplnService.remove(aplnId),
|
||||
mutationFn: ({ aplnId }: { aplnId: string; elaprTmpltId: string }) =>
|
||||
elaprAplnService.remove(aplnId),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -160,12 +159,8 @@ export function useReorderElaprApln() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
body,
|
||||
}: {
|
||||
body: ElaprAplnReorderRequest;
|
||||
elaprTmpltId: string;
|
||||
}) => elaprAplnService.reorder(body),
|
||||
mutationFn: ({ body }: { body: ElaprAplnReorderRequest; elaprTmpltId: string }) =>
|
||||
elaprAplnService.reorder(body),
|
||||
onSuccess: (_data, { elaprTmpltId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprAplnBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
@@ -174,3 +169,194 @@ export function useReorderElaprApln() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDmndPage(params: ElaprDmndSearchDto) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findPage", params],
|
||||
queryFn: () => elaprDmndService.findPage(params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDmnd(elaprDmndId: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findById", elaprDmndId],
|
||||
queryFn: () => elaprDmndService.findById(elaprDmndId!),
|
||||
enabled: Boolean(elaprDmndId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprDocPrgrsByDocNo(elaprDocNo: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprDmndBaseKey, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
queryFn: () => elaprDmndService.findDocPrgrsByDocNo(elaprDocNo!),
|
||||
enabled: Boolean(elaprDocNo),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveElaprDmnd() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (body: ElaprDmndSaveRequestDto) => elaprDmndService.save(body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateElaprDmnd() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ elaprDmndId, body }: { elaprDmndId: string; body: ElaprDmndSaveRequestDto }) =>
|
||||
elaprDmndService.update(elaprDmndId, body),
|
||||
onSuccess: (_data, { elaprDmndId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...elaprDmndBaseKey, "findById", elaprDmndId],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateElaprDmndStatus() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ elaprDmndId, atrzSttsComCd }: { elaprDmndId: string; atrzSttsComCd: string }) =>
|
||||
elaprDmndService.updateStatus(elaprDmndId, atrzSttsComCd),
|
||||
onSuccess: (_data, { elaprDmndId }) => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...elaprDmndBaseKey, "findById", elaprDmndId],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemoveElaprDmnd() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (elaprDmndId: string) => elaprDmndService.remove(elaprDmndId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: elaprDmndBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprPrgrsSttsByDmndId(elaprDmndId: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprPrgrsSttsBaseKey, "findAllByDmndId", elaprDmndId],
|
||||
queryFn: () => elaprPrgrsSttsService.findAllByDmndId(elaprDmndId!),
|
||||
enabled: Boolean(elaprDmndId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprPrgrsStts(elaprPrgrsId: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprPrgrsSttsBaseKey, elaprPrgrsId],
|
||||
queryFn: () => elaprPrgrsSttsService.findById(elaprPrgrsId!),
|
||||
enabled: Boolean(elaprPrgrsId),
|
||||
});
|
||||
}
|
||||
|
||||
export function useElaprPrgrsSttsMyDocPage(params: ElaprPrgrsSttsMyDocSearchParams) {
|
||||
return useQuery({
|
||||
queryKey: [...elaprPrgrsSttsBaseKey, "findMyElaperDocList", params],
|
||||
queryFn: () => elaprPrgrsSttsService.findMyElaperDocList(params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveElaprPrgrsStts() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (requestDtos: ElaprPrgrsSttsSaveRequestDto[]) =>
|
||||
elaprPrgrsSttsService.save(requestDtos),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useProcessElaprPrgrsStts() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprPrgrsId,
|
||||
body,
|
||||
}: {
|
||||
elaprPrgrsId: string;
|
||||
body: ElaprProcessRequestDto;
|
||||
}) => elaprPrgrsSttsService.process(elaprPrgrsId, body),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useProcessElaprPrgrsSttsByDoc() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprDocNo,
|
||||
aprvrUuid,
|
||||
body,
|
||||
}: {
|
||||
elaprDocNo: string;
|
||||
aprvrUuid: string;
|
||||
body: ElaprProcessRequestDto;
|
||||
}) => elaprPrgrsSttsService.processByDocNoAndAprvrUuid(elaprDocNo, aprvrUuid, body),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteElaprPrgrsStts() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (elaprPrgrsId: string) => elaprPrgrsSttsService.delete(elaprPrgrsId),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateElaprPrgrsSttsApprover() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprPrgrsId,
|
||||
body,
|
||||
}: {
|
||||
elaprPrgrsId: string;
|
||||
body: ElaprApproverChangeRequestDto;
|
||||
}) => elaprPrgrsSttsService.updateApprover(elaprPrgrsId, body),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInsertElaprPrgrsSttsApprover() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
elaprDmndId,
|
||||
body,
|
||||
}: {
|
||||
elaprDmndId: string;
|
||||
body: ElaprApproverInsertRequestDto;
|
||||
}) => elaprPrgrsSttsService.insertApprover(elaprDmndId, body),
|
||||
onSuccess: () => {
|
||||
invalidateElaprPrgrsSttsQueries(queryClient);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,33 +1,122 @@
|
||||
export { elaprAplnService } from "./api/elapr-apln-service";
|
||||
export { elaprTmpltService } from "./api/elapr-tmplt-service";
|
||||
export {
|
||||
useCreateElaprTmplt,
|
||||
useDeleteElaprApln,
|
||||
useDeleteElaprTmplt,
|
||||
useElaprApln,
|
||||
useElaprAplnPage,
|
||||
useElaprTmplt,
|
||||
useElaprTmpltPage,
|
||||
useReorderElaprApln,
|
||||
useSaveMultiElaprApln,
|
||||
useUpdateElaprApln,
|
||||
useUpdateElaprTmplt,
|
||||
} from "./api/queries";
|
||||
export {
|
||||
ELAPR_APLN_SAVE_MODE,
|
||||
type ElaprApln,
|
||||
type ElaprAplnListParams,
|
||||
type ElaprAplnReorderItem,
|
||||
type ElaprAplnReorderRequest,
|
||||
type ElaprAplnSaveMode,
|
||||
type ElaprAplnSaveRequest,
|
||||
type ElaprAplnService,
|
||||
type ElaprAplnUpdateRequest,
|
||||
} from "./model/elapr-apln-interface";
|
||||
export type {
|
||||
ElaprTmplt,
|
||||
ElaprTmpltSaveRequest,
|
||||
ElaprTmpltSearchParams,
|
||||
ElaprTmpltService,
|
||||
ElaprTmpltUpdateRequest,
|
||||
} from "./model/elapr-tmplt-interface";
|
||||
export { elaprAplnService } from "./api/elapr-apln-service";
|
||||
export { elaprDmndService } from "./api/elapr-dmnd-service";
|
||||
export { elaprPrgrsSttsService } from "./api/elapr-prgrs-stts-service";
|
||||
export { elaprTmpltService } from "./api/elapr-tmplt-service";
|
||||
export {
|
||||
useCreateElaprTmplt,
|
||||
useDeleteElaprApln,
|
||||
useDeleteElaprPrgrsStts,
|
||||
useDeleteElaprTmplt,
|
||||
useElaprApln,
|
||||
useElaprAplnPage,
|
||||
useElaprDmnd,
|
||||
useElaprDmndPage,
|
||||
useElaprDocPrgrsByDocNo,
|
||||
useElaprPrgrsStts,
|
||||
useElaprPrgrsSttsByDmndId,
|
||||
useElaprPrgrsSttsMyDocPage,
|
||||
useElaprTmplt,
|
||||
useElaprTmpltPage,
|
||||
useInsertElaprPrgrsSttsApprover,
|
||||
useProcessElaprPrgrsStts,
|
||||
useProcessElaprPrgrsSttsByDoc,
|
||||
useRemoveElaprDmnd,
|
||||
useReorderElaprApln,
|
||||
useSaveElaprDmnd,
|
||||
useSaveElaprPrgrsStts,
|
||||
useSaveMultiElaprApln,
|
||||
useUpdateElaprApln,
|
||||
useUpdateElaprDmnd,
|
||||
useUpdateElaprDmndStatus,
|
||||
useUpdateElaprPrgrsSttsApprover,
|
||||
useUpdateElaprTmplt,
|
||||
} from "./api/queries";
|
||||
export {
|
||||
assignAtrzSeqNumbers,
|
||||
buildSaveMultiPayload,
|
||||
hasUnsavedChanges,
|
||||
} from "./lib/apln-save-utils";
|
||||
export {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAplnRow,
|
||||
validateAtrzOrder,
|
||||
validateUniqueSeqNo,
|
||||
} from "./lib/apln-validators";
|
||||
export {
|
||||
findCurrentUserPendingApprovalRequest,
|
||||
hasCurrentUserPendingApprovalRequest,
|
||||
} from "./lib/approval-request-utils";
|
||||
export {
|
||||
APPROVER_STATUS_MAP,
|
||||
type ApproverStatusConfig,
|
||||
getApproverStatus,
|
||||
sortByAtrzSeqNo,
|
||||
} from "./lib/approval-utils";
|
||||
export {
|
||||
buildElaprPrgrsSttsListFromApln,
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
} from "./lib/dmnd-save-utils";
|
||||
export type { ElaprPrgrsSttsRow } from "./lib/prgrs-stts-save-utils";
|
||||
export {
|
||||
assignPrgrsSttsAtrzSeqNumbers,
|
||||
buildPrgrsSttsSavePayload,
|
||||
hasUnsavedPrgrsSttsChanges,
|
||||
isPrgrsSttsRowEditable,
|
||||
PRGRS_STTS_WAITING_CD,
|
||||
validateAllPrgrsSttsRows,
|
||||
validatePrgrsSttsRow,
|
||||
} from "./lib/prgrs-stts-save-utils";
|
||||
export { createDraftRowKey, useAplnRows } from "./lib/use-apln-rows";
|
||||
export { useDndSensors } from "./lib/use-dnd-sensors";
|
||||
export { usePrgrsSttsRows } from "./lib/use-prgrs-stts-rows";
|
||||
export {
|
||||
ELAPR_APLN_SAVE_MODE,
|
||||
type ElaprApln,
|
||||
type ElaprAplnListParams,
|
||||
type ElaprAplnReorderItem,
|
||||
type ElaprAplnReorderRequest,
|
||||
type ElaprAplnSaveMode,
|
||||
type ElaprAplnSaveRequest,
|
||||
type ElaprAplnService,
|
||||
type ElaprAplnUpdateRequest,
|
||||
} from "./model/elapr-apln-interface";
|
||||
export type { ElaprAplnRow } from "./model/elapr-apln-row-types";
|
||||
export type {
|
||||
ElaprDmndResponseDto,
|
||||
ElaprDmndSaveRequestDto,
|
||||
ElaprDmndSearchDto,
|
||||
ElaprDmndService,
|
||||
ElaprDocPrgrsResponseDto,
|
||||
} from "./model/elapr-dmnd-interface";
|
||||
export type {
|
||||
ElaprApproverChangeRequestDto,
|
||||
ElaprApproverInsertRequestDto,
|
||||
ElaprPrgrsSttsMyDocSearchParams,
|
||||
ElaprPrgrsSttsResponseDto,
|
||||
ElaprPrgrsSttsSaveMode,
|
||||
ElaprPrgrsSttsSaveRequestDto,
|
||||
ElaprPrgrsSttsSearchDto,
|
||||
ElaprPrgrsSttsService,
|
||||
ElaprProcessRequestDto,
|
||||
} from "./model/elapr-prgrs-stts-interface";
|
||||
export { ELAPR_PRGRS_STTS_SAVE_MODE } from "./model/elapr-prgrs-stts-interface";
|
||||
export type {
|
||||
ElaprTmplt,
|
||||
ElaprTmpltSaveRequest,
|
||||
ElaprTmpltSearchParams,
|
||||
ElaprTmpltService,
|
||||
ElaprTmpltUpdateRequest,
|
||||
} from "./model/elapr-tmplt-interface";
|
||||
export type { ReviewerOption } from "./model/reviewer-option";
|
||||
export { AprvrPickerDialog } from "./ui/aprvr-picker-dialog";
|
||||
export { ElaprAplnPickerGrid } from "./ui/elapr-apln-picker-grid";
|
||||
export { ElaprApprovalDialog } from "./ui/elapr-approval-dialog";
|
||||
export { ElaprDocPrgrsStatus } from "./ui/elapr-doc-prgrs-status";
|
||||
export {
|
||||
type ElaprTmpltPickerConfirmPayload,
|
||||
ElaprTmpltPickerDialog,
|
||||
} from "./ui/elapr-tmplt-picker-dialog";
|
||||
export { ElaprTmpltSelectTable } from "./ui/elapr-tmplt-select-table";
|
||||
export { SortableAplnPickerRow } from "./ui/sortable-apln-picker-row";
|
||||
export { SortablePrgrsSttsRow } from "./ui/sortable-prgrs-stts-row";
|
||||
|
||||
+31
-4
@@ -1,8 +1,16 @@
|
||||
import { ELAPR_APLN_SAVE_MODE, type ElaprApln, type ElaprAplnSaveRequest } from "@/entities/elapr";
|
||||
import {
|
||||
ELAPR_APLN_SAVE_MODE,
|
||||
type ElaprApln,
|
||||
type ElaprAplnSaveRequest,
|
||||
} from "../model/elapr-apln-interface";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
|
||||
import { assignAtrzSeqNumbers } from "./assign-atrz-seq";
|
||||
import { getVisibleRows } from "./validators";
|
||||
import type { ElaprAplnRow } from "../model/apln-row-types";
|
||||
import { getVisibleRows } from "./apln-validators";
|
||||
|
||||
/** 그리드 행 순서(1-based)로 결재순서번호 일괄 채번 */
|
||||
export function assignAtrzSeqNumbers(rows: ElaprAplnRow[]): ElaprAplnRow[] {
|
||||
return rows.map((row, index) => ({ ...row, atrzSeqNo: index + 1 }));
|
||||
}
|
||||
|
||||
function rowDiffersFromServer(row: ElaprAplnRow, serverRow: ElaprApln): boolean {
|
||||
return (
|
||||
@@ -22,6 +30,25 @@ function shouldIncludeUpdate(row: ElaprAplnRow, serverRows: ElaprApln[]): boolea
|
||||
return rowDiffersFromServer(row, serverRow);
|
||||
}
|
||||
|
||||
export function hasUnsavedChanges(rows: ElaprAplnRow[], serverRows: ElaprApln[]): boolean {
|
||||
if (rows.some((row) => row.isDeleted && row.aplnId)) return true;
|
||||
if (rows.some((row) => row.isNew && !row.isDeleted)) return true;
|
||||
|
||||
const visibleWithSeq = assignAtrzSeqNumbers(getVisibleRows(rows));
|
||||
|
||||
for (const row of visibleWithSeq) {
|
||||
if (row.isNew) continue;
|
||||
if (!row.aplnId) continue;
|
||||
if (row.isDirty) return true;
|
||||
|
||||
const serverRow = serverRows.find((s) => s.aplnId === row.aplnId);
|
||||
if (!serverRow) continue;
|
||||
if (rowDiffersFromServer(row, serverRow)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function buildSaveMultiPayload(
|
||||
rows: ElaprAplnRow[],
|
||||
elaprTmpltId: string,
|
||||
+8
-16
@@ -1,23 +1,19 @@
|
||||
import type { ElaprAplnRow } from "../model/apln-row-types";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
|
||||
const ATRZ_RECEIVER_CD = "ATRZ01";
|
||||
const ATRZ_FINAL_CD = "ATRZ99";
|
||||
|
||||
interface AtrzOrderValidateRow {
|
||||
atrzSeComCd?: string;
|
||||
isDeleted?: boolean;
|
||||
}
|
||||
|
||||
export function getVisibleRows(rows: ElaprAplnRow[]): ElaprAplnRow[] {
|
||||
return rows.filter((row) => !row.isDeleted);
|
||||
}
|
||||
|
||||
/** 접수자·최종승인자 위치·중복 검증 (최종 저장 시점) */
|
||||
export function validateAtrzOrder(rows: ElaprAplnRow[]): string | null {
|
||||
const visibleRows = getVisibleRows(rows);
|
||||
|
||||
const receiverCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_RECEIVER_CD).length;
|
||||
if (receiverCount === 0) {
|
||||
return "접수자는 반드시 등록해야 합니다.";
|
||||
}
|
||||
if (receiverCount > 1) {
|
||||
return "접수자는 한 명만 등록할 수 있습니다.";
|
||||
}
|
||||
export function validateAtrzOrder(rows: AtrzOrderValidateRow[]): string | null {
|
||||
const visibleRows = rows.filter((row) => !row.isDeleted);
|
||||
|
||||
const finalCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_FINAL_CD).length;
|
||||
if (finalCount === 0) {
|
||||
@@ -27,10 +23,6 @@ export function validateAtrzOrder(rows: ElaprAplnRow[]): string | null {
|
||||
return "최종승인자는 한 명만 등록할 수 있습니다.";
|
||||
}
|
||||
|
||||
if (receiverCount === 1 && visibleRows[0]?.atrzSeComCd !== ATRZ_RECEIVER_CD) {
|
||||
return "접수자는 결재선 첫 번째에 배치해야 합니다.";
|
||||
}
|
||||
|
||||
if (finalCount === 1) {
|
||||
const lastRow = visibleRows[visibleRows.length - 1];
|
||||
if (lastRow?.atrzSeComCd !== ATRZ_FINAL_CD) {
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { ElaprDocPrgrsResponseDto } from "../model/elapr-dmnd-interface";
|
||||
|
||||
export function hasCurrentUserPendingApprovalRequest(
|
||||
list: ElaprDocPrgrsResponseDto[],
|
||||
currentUserUuid: string | undefined,
|
||||
): boolean {
|
||||
if (!currentUserUuid) return false;
|
||||
|
||||
return list.some(
|
||||
(row) => row.aprvrUuid === currentUserUuid && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002",
|
||||
);
|
||||
}
|
||||
|
||||
export function findCurrentUserPendingApprovalRequest(
|
||||
list?: ElaprDocPrgrsResponseDto[],
|
||||
currentUserUuid?: string | undefined,
|
||||
): ElaprDocPrgrsResponseDto | undefined {
|
||||
if (!currentUserUuid) return undefined;
|
||||
|
||||
return list?.find(
|
||||
(row) => row.aprvrUuid === currentUserUuid && row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_002",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model";
|
||||
|
||||
export interface ApproverStatusConfig {
|
||||
label: string;
|
||||
badgeClass: string;
|
||||
}
|
||||
|
||||
export const APPROVER_STATUS_MAP: Record<string, ApproverStatusConfig> = {
|
||||
ATRZ_PRGRS_001: { label: "대기", badgeClass: "bg-gray-100 text-gray-700" },
|
||||
ATRZ_PRGRS_002: { label: "요청", badgeClass: "bg-yellow-100 text-yellow-700" },
|
||||
ATRZ_PRGRS_003: { label: "승인", badgeClass: "bg-green-100 text-green-700" },
|
||||
ATRZ_PRGRS_004: { label: "반려", badgeClass: "bg-red-100 text-red-700" },
|
||||
ATRZ_PRGRS_999: { label: "결재안함", badgeClass: "bg-blue-100 text-blue-700" },
|
||||
};
|
||||
|
||||
export function sortByAtrzSeqNo(
|
||||
list: ElaprDocPrgrsClientResponseDto[],
|
||||
): ElaprDocPrgrsClientResponseDto[] {
|
||||
return list.slice().sort((a, b) => Number(a.atrzSeqNo ?? 0) - Number(b.atrzSeqNo ?? 0));
|
||||
}
|
||||
|
||||
export function getApproverStatus(status: string | undefined): ApproverStatusConfig {
|
||||
return (
|
||||
APPROVER_STATUS_MAP[status ?? ""] ?? {
|
||||
label: status || "-",
|
||||
badgeClass: "bg-gray-100 text-gray-700",
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ElaprApln } from "../model/elapr-apln-interface";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
import type { ElaprPrgrsSttsSaveRequestDto } from "../model/elapr-prgrs-stts-interface";
|
||||
|
||||
import { getVisibleRows } from "./apln-validators";
|
||||
|
||||
function toPrgrsSttsItem(
|
||||
aprvrUuid: string | undefined,
|
||||
atrzSeComCd: string | undefined,
|
||||
atrzSeqNo: number | undefined,
|
||||
): ElaprPrgrsSttsSaveRequestDto {
|
||||
if (!aprvrUuid?.trim()) {
|
||||
throw new Error("결재자가 지정되지 않은 행이 있습니다.");
|
||||
}
|
||||
if (!atrzSeComCd?.trim()) {
|
||||
throw new Error("결재구분이 지정되지 않은 행이 있습니다.");
|
||||
}
|
||||
if (atrzSeqNo == null) {
|
||||
throw new Error("결재순서번호가 지정되지 않은 행이 있습니다.");
|
||||
}
|
||||
|
||||
return {
|
||||
aprvrUuid,
|
||||
atrzSeComCd,
|
||||
atrzSeqNo,
|
||||
};
|
||||
}
|
||||
|
||||
/** 서식 결재선 API 응답 → 기안 요청용 진행상태 목록 */
|
||||
export function buildElaprPrgrsSttsListFromApln(
|
||||
items: ElaprApln[],
|
||||
): ElaprPrgrsSttsSaveRequestDto[] {
|
||||
return items.map((item) => toPrgrsSttsItem(item.aprvrUuid, item.atrzSeComCd, item.atrzSeqNo));
|
||||
}
|
||||
|
||||
/** 결재선 그리드 행 → 기안 요청용 진행상태 목록 (서버가 seq 기준 상태 자동 결정) */
|
||||
export function buildElaprPrgrsSttsListFromAplnRows(
|
||||
rows: ElaprAplnRow[],
|
||||
): ElaprPrgrsSttsSaveRequestDto[] {
|
||||
return getVisibleRows(rows).map((row) =>
|
||||
toPrgrsSttsItem(row.aprvrUuid, row.atrzSeComCd, row.atrzSeqNo),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import {
|
||||
ELAPR_PRGRS_STTS_SAVE_MODE,
|
||||
type ElaprPrgrsSttsSaveRequestDto,
|
||||
} from "../model/elapr-prgrs-stts-interface";
|
||||
|
||||
export const PRGRS_STTS_WAITING_CD = "ATRZ_PRGRS_001";
|
||||
export const PRGRS_STTS_APPROVED_CD = "ATRZ_PRGRS_003";
|
||||
|
||||
/** 서버 행 비교용 최소 필드 */
|
||||
export interface PrgrsSttsServerRow {
|
||||
elaprPrgrsId?: string;
|
||||
aprvrUuid?: string;
|
||||
atrzSeComCd?: string;
|
||||
atrzSeqNo?: number;
|
||||
dcarbYn?: string;
|
||||
}
|
||||
|
||||
/** 결재선 그리드 로컬 행 — 진행상태 API 응답 + UI 보조 필드 */
|
||||
export interface ElaprPrgrsSttsRow {
|
||||
rowKey: string;
|
||||
elaprPrgrsId?: string;
|
||||
elaprDmndId?: string;
|
||||
aprvrUuid?: string;
|
||||
aprvrNm?: string;
|
||||
atrzSeComCd?: string;
|
||||
atrzSeComNm?: string;
|
||||
atrzSeqNo?: number;
|
||||
atrzPrgrsSttsComCd?: string;
|
||||
atrzPrgrsSttsComNm?: string;
|
||||
rjctRsn?: string | null;
|
||||
ognzNm?: string;
|
||||
jbpsSeNm?: string;
|
||||
jbgdSeNm?: string;
|
||||
dcarbYn?: string;
|
||||
isNew?: boolean;
|
||||
isDirty?: boolean;
|
||||
/** 소프트 삭제 — 최종 저장 시 mode:D */
|
||||
isDeleted?: boolean;
|
||||
/** 수정 취소용 스냅샷 */
|
||||
snapshot?: ElaprPrgrsSttsRow;
|
||||
}
|
||||
|
||||
export function isPrgrsSttsRowEditable(row: ElaprPrgrsSttsRow): boolean {
|
||||
return row.isNew === true || row.atrzPrgrsSttsComCd === PRGRS_STTS_WAITING_CD;
|
||||
}
|
||||
|
||||
export function isPrgrsSttsRowDcarbable(row: ElaprPrgrsSttsRow): boolean {
|
||||
return row.atrzPrgrsSttsComCd !== PRGRS_STTS_APPROVED_CD;
|
||||
}
|
||||
|
||||
function getVisibleRows(rows: ElaprPrgrsSttsRow[]): ElaprPrgrsSttsRow[] {
|
||||
return rows.filter((row) => !row.isDeleted);
|
||||
}
|
||||
|
||||
/** 그리드 행 순서(1-based)로 결재순서번호 일괄 채번 */
|
||||
export function assignPrgrsSttsAtrzSeqNumbers(rows: ElaprPrgrsSttsRow[]): ElaprPrgrsSttsRow[] {
|
||||
return rows.map((row, index) => ({ ...row, atrzSeqNo: index + 1 }));
|
||||
}
|
||||
|
||||
export function validatePrgrsSttsRow(row: ElaprPrgrsSttsRow): string | null {
|
||||
if (!row.atrzSeComCd?.trim()) {
|
||||
return "결재구분을 선택해 주세요.";
|
||||
}
|
||||
if (!row.aprvrUuid?.trim()) {
|
||||
return "결재자를 선택해 주세요.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function validateAllPrgrsSttsRows(
|
||||
rows: ElaprPrgrsSttsRow[],
|
||||
isEditingLocked: boolean,
|
||||
): string | null {
|
||||
if (isEditingLocked) {
|
||||
return "편집 중인 행을 먼저 저장하거나 취소해 주세요.";
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(rows);
|
||||
if (visibleRows.length === 0) {
|
||||
return "저장할 결재선이 없습니다.";
|
||||
}
|
||||
|
||||
for (const row of visibleRows) {
|
||||
const fieldError = validatePrgrsSttsRow(row);
|
||||
if (fieldError) return fieldError;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function rowDiffersFromServer(row: ElaprPrgrsSttsRow, serverRow: PrgrsSttsServerRow): boolean {
|
||||
return (
|
||||
row.aprvrUuid !== serverRow.aprvrUuid ||
|
||||
row.atrzSeComCd !== serverRow.atrzSeComCd ||
|
||||
row.atrzSeqNo !== serverRow.atrzSeqNo ||
|
||||
(row.dcarbYn ?? "N") !== (serverRow.dcarbYn ?? "N")
|
||||
);
|
||||
}
|
||||
|
||||
function shouldIncludeUpdate(row: ElaprPrgrsSttsRow, serverRows: PrgrsSttsServerRow[]): boolean {
|
||||
if (row.isDirty) return true;
|
||||
if (!row.elaprPrgrsId) return false;
|
||||
|
||||
const serverRow = serverRows.find((s) => s.elaprPrgrsId === row.elaprPrgrsId);
|
||||
if (!serverRow) return false;
|
||||
|
||||
return rowDiffersFromServer(row, serverRow);
|
||||
}
|
||||
|
||||
export function hasUnsavedPrgrsSttsChanges(
|
||||
rows: ElaprPrgrsSttsRow[],
|
||||
serverRows: PrgrsSttsServerRow[],
|
||||
): boolean {
|
||||
if (rows.some((row) => row.isDeleted && row.elaprPrgrsId)) return true;
|
||||
if (rows.some((row) => row.isNew && !row.isDeleted)) return true;
|
||||
|
||||
const visibleWithSeq = assignPrgrsSttsAtrzSeqNumbers(getVisibleRows(rows));
|
||||
|
||||
for (const row of visibleWithSeq) {
|
||||
if (row.isNew) continue;
|
||||
if (!row.elaprPrgrsId) continue;
|
||||
if (row.isDirty) return true;
|
||||
|
||||
const serverRow = serverRows.find((s) => s.elaprPrgrsId === row.elaprPrgrsId);
|
||||
if (!serverRow) continue;
|
||||
if (rowDiffersFromServer(row, serverRow)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function buildPrgrsSttsSavePayload(
|
||||
rows: ElaprPrgrsSttsRow[],
|
||||
elaprDmndId: string,
|
||||
serverRows: PrgrsSttsServerRow[],
|
||||
): ElaprPrgrsSttsSaveRequestDto[] {
|
||||
const payload: ElaprPrgrsSttsSaveRequestDto[] = [];
|
||||
|
||||
for (const row of rows) {
|
||||
if (row.isDeleted && row.elaprPrgrsId) {
|
||||
payload.push({
|
||||
mode: ELAPR_PRGRS_STTS_SAVE_MODE.DELETE,
|
||||
elaprPrgrsId: row.elaprPrgrsId,
|
||||
elaprDmndId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const visibleWithSeq = assignPrgrsSttsAtrzSeqNumbers(getVisibleRows(rows));
|
||||
|
||||
for (const row of visibleWithSeq) {
|
||||
if (row.isNew) {
|
||||
payload.push({
|
||||
mode: ELAPR_PRGRS_STTS_SAVE_MODE.CREATE,
|
||||
elaprDmndId,
|
||||
aprvrUuid: row.aprvrUuid,
|
||||
atrzSeComCd: row.atrzSeComCd,
|
||||
atrzSeqNo: row.atrzSeqNo,
|
||||
atrzPrgrsSttsComCd: row.atrzPrgrsSttsComCd,
|
||||
dcarbYn: row.dcarbYn ?? "N",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!row.elaprPrgrsId) continue;
|
||||
|
||||
if (shouldIncludeUpdate(row, serverRows)) {
|
||||
payload.push({
|
||||
mode: ELAPR_PRGRS_STTS_SAVE_MODE.UPDATE,
|
||||
elaprPrgrsId: row.elaprPrgrsId,
|
||||
elaprDmndId,
|
||||
aprvrUuid: row.aprvrUuid,
|
||||
atrzSeComCd: row.atrzSeComCd,
|
||||
atrzSeqNo: row.atrzSeqNo,
|
||||
atrzPrgrsSttsComCd: row.atrzPrgrsSttsComCd,
|
||||
dcarbYn: row.dcarbYn ?? "N",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
+25
-20
@@ -4,12 +4,11 @@ import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
|
||||
import type { ElaprApln } from "@/entities/elapr";
|
||||
import type { ElaprApln } from "../model/elapr-apln-interface";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
|
||||
import type { ElaprAplnRow } from "../model/apln-row-types";
|
||||
|
||||
import { assignAtrzSeqNumbers } from "./assign-atrz-seq";
|
||||
import { getVisibleRows } from "./validators";
|
||||
import { assignAtrzSeqNumbers } from "./apln-save-utils";
|
||||
import { getVisibleRows } from "./apln-validators";
|
||||
|
||||
function toRow(item: ElaprApln): ElaprAplnRow {
|
||||
return {
|
||||
@@ -101,7 +100,13 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
|
||||
return prev.map((row) => {
|
||||
if (row.rowKey !== editingRowKey) return row;
|
||||
if (row.snapshot) {
|
||||
const { snapshot: _s, isNew: _n, isDirty: _d, isDeleted: _del, ...restored } = row.snapshot;
|
||||
const {
|
||||
snapshot: _s,
|
||||
isNew: _n,
|
||||
isDirty: _d,
|
||||
isDeleted: _del,
|
||||
...restored
|
||||
} = row.snapshot;
|
||||
return { ...restored, isNew: false, isDirty: false, isDeleted: false };
|
||||
}
|
||||
return { ...row, isDirty: false };
|
||||
@@ -110,19 +115,16 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
|
||||
setEditingRowKey(null);
|
||||
}, [editingRowKey]);
|
||||
|
||||
const commitEdit = useCallback(
|
||||
(rowKey: string) => {
|
||||
setRows((prev) =>
|
||||
prev.map((row) => {
|
||||
if (row.rowKey !== rowKey) return row;
|
||||
const { snapshot: _s, ...committed } = row;
|
||||
return { ...committed, isDirty: true };
|
||||
}),
|
||||
);
|
||||
setEditingRowKey(null);
|
||||
},
|
||||
[],
|
||||
);
|
||||
const commitEdit = useCallback((rowKey: string) => {
|
||||
setRows((prev) =>
|
||||
prev.map((row) => {
|
||||
if (row.rowKey !== rowKey) return row;
|
||||
const { snapshot: _s, ...committed } = row;
|
||||
return { ...committed, isDirty: true };
|
||||
}),
|
||||
);
|
||||
setEditingRowKey(null);
|
||||
}, []);
|
||||
|
||||
const markDeleted = useCallback((rowKey: string) => {
|
||||
setRows((prev) => {
|
||||
@@ -158,7 +160,10 @@ export function useAplnRows({ elaprTmpltId, serverRows }: UseAplnRowsOptions) {
|
||||
|
||||
const visibleKeys = new Set(reorderedVisible.map((r) => r.rowKey));
|
||||
const deletedRows = prev.filter((r) => r.isDeleted);
|
||||
const merged = [...reorderedVisible, ...deletedRows.filter((r) => !visibleKeys.has(r.rowKey))];
|
||||
const merged = [
|
||||
...reorderedVisible,
|
||||
...deletedRows.filter((r) => !visibleKeys.has(r.rowKey)),
|
||||
];
|
||||
|
||||
payloadRows = reorderedVisible;
|
||||
return merged;
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { KeyboardSensor, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
||||
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model";
|
||||
|
||||
import {
|
||||
assignPrgrsSttsAtrzSeqNumbers,
|
||||
type ElaprPrgrsSttsRow,
|
||||
isPrgrsSttsRowEditable,
|
||||
PRGRS_STTS_WAITING_CD,
|
||||
} from "./prgrs-stts-save-utils";
|
||||
import { createDraftRowKey } from "./use-apln-rows";
|
||||
|
||||
function getVisibleRows(rows: ElaprPrgrsSttsRow[]): ElaprPrgrsSttsRow[] {
|
||||
return rows.filter((row) => !row.isDeleted);
|
||||
}
|
||||
|
||||
function toRow(item: ElaprDocPrgrsClientResponseDto): ElaprPrgrsSttsRow {
|
||||
return {
|
||||
rowKey: item.elaprPrgrsId ?? createDraftRowKey(),
|
||||
elaprPrgrsId: item.elaprPrgrsId,
|
||||
elaprDmndId: item.elaprDmndId,
|
||||
aprvrUuid: item.aprvrUuid,
|
||||
aprvrNm: item.aprvrNm,
|
||||
atrzSeComCd: item.atrzSeComCd,
|
||||
atrzSeComNm: item.atrzSeComNm,
|
||||
atrzSeqNo: item.atrzSeqNo,
|
||||
atrzPrgrsSttsComCd: item.atrzPrgrsSttsComCd,
|
||||
atrzPrgrsSttsComNm: item.atrzPrgrsSttsComNm,
|
||||
rjctRsn: item.rjctRsn,
|
||||
ognzNm: item.ognzNm,
|
||||
jbpsSeNm: item.jbpsSeNm,
|
||||
jbgdSeNm: item.jbgdSeNm,
|
||||
dcarbYn: item.dcarbYn ?? "N",
|
||||
isNew: false,
|
||||
isDirty: false,
|
||||
isDeleted: false,
|
||||
};
|
||||
}
|
||||
|
||||
interface UsePrgrsSttsRowsOptions {
|
||||
serverRows?: ElaprDocPrgrsClientResponseDto[];
|
||||
elaprDmndId?: string | null;
|
||||
}
|
||||
|
||||
export function usePrgrsSttsRows({ serverRows, elaprDmndId }: UsePrgrsSttsRowsOptions) {
|
||||
const [rows, setRows] = useState<ElaprPrgrsSttsRow[]>([]);
|
||||
const [editingRowKey, setEditingRowKey] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setRows((serverRows ?? []).map(toRow));
|
||||
setEditingRowKey(null);
|
||||
}, [serverRows]);
|
||||
|
||||
const isEditingLocked = editingRowKey != null;
|
||||
const visibleRows = getVisibleRows(rows);
|
||||
|
||||
const addRow = useCallback(() => {
|
||||
if (!elaprDmndId || isEditingLocked) return;
|
||||
|
||||
const rowKey = createDraftRowKey();
|
||||
|
||||
setRows((prev) => [
|
||||
...prev,
|
||||
{
|
||||
rowKey,
|
||||
elaprDmndId,
|
||||
atrzSeComCd: "",
|
||||
aprvrUuid: "",
|
||||
aprvrNm: "",
|
||||
atrzPrgrsSttsComCd: PRGRS_STTS_WAITING_CD,
|
||||
dcarbYn: "N",
|
||||
isNew: true,
|
||||
isDirty: true,
|
||||
isDeleted: false,
|
||||
},
|
||||
]);
|
||||
setEditingRowKey(rowKey);
|
||||
}, [elaprDmndId, isEditingLocked]);
|
||||
|
||||
const startEdit = useCallback(
|
||||
(rowKey: string) => {
|
||||
if (isEditingLocked) return;
|
||||
|
||||
const target = rows.find((r) => r.rowKey === rowKey);
|
||||
if (!target || !isPrgrsSttsRowEditable(target)) return;
|
||||
|
||||
setEditingRowKey(rowKey);
|
||||
setRows((prev) =>
|
||||
prev.map((row) => (row.rowKey === rowKey ? { ...row, snapshot: { ...row } } : row)),
|
||||
);
|
||||
},
|
||||
[isEditingLocked, rows],
|
||||
);
|
||||
|
||||
const updateRow = useCallback(
|
||||
(rowKey: string, patch: Partial<ElaprPrgrsSttsRow>) => {
|
||||
if (editingRowKey !== rowKey) return;
|
||||
setRows((prev) =>
|
||||
prev.map((row) => (row.rowKey === rowKey ? { ...row, ...patch, isDirty: true } : row)),
|
||||
);
|
||||
},
|
||||
[editingRowKey],
|
||||
);
|
||||
|
||||
const setDcarbYn = useCallback(
|
||||
(rowKey: string, checked: boolean) => {
|
||||
if (editingRowKey != null && editingRowKey !== rowKey) return;
|
||||
|
||||
setRows((prev) => {
|
||||
const target = prev.find((r) => r.rowKey === rowKey);
|
||||
if (!target || target.isDeleted) return prev;
|
||||
|
||||
const nextYn = checked ? "Y" : "N";
|
||||
|
||||
return prev.map((row) => {
|
||||
if (row.isDeleted) return row;
|
||||
|
||||
if (row.rowKey === rowKey) {
|
||||
if ((row.dcarbYn ?? "N") === nextYn) return row;
|
||||
return { ...row, dcarbYn: nextYn, isDirty: true };
|
||||
}
|
||||
|
||||
if (!checked) return row;
|
||||
if ((row.dcarbYn ?? "N") === "N") return row;
|
||||
return { ...row, dcarbYn: "N", isDirty: true };
|
||||
});
|
||||
});
|
||||
},
|
||||
[editingRowKey],
|
||||
);
|
||||
|
||||
const cancelEdit = useCallback(() => {
|
||||
if (!editingRowKey) return;
|
||||
|
||||
setRows((prev) => {
|
||||
const target = prev.find((r) => r.rowKey === editingRowKey);
|
||||
if (target?.isNew) {
|
||||
return prev.filter((r) => r.rowKey !== editingRowKey);
|
||||
}
|
||||
return prev.map((row) => {
|
||||
if (row.rowKey !== editingRowKey) return row;
|
||||
if (row.snapshot) {
|
||||
const {
|
||||
snapshot: _s,
|
||||
isNew: _n,
|
||||
isDirty: _d,
|
||||
isDeleted: _del,
|
||||
...restored
|
||||
} = row.snapshot;
|
||||
return { ...restored, isNew: false, isDirty: false, isDeleted: false };
|
||||
}
|
||||
return { ...row, isDirty: false };
|
||||
});
|
||||
});
|
||||
setEditingRowKey(null);
|
||||
}, [editingRowKey]);
|
||||
|
||||
const commitEdit = useCallback((rowKey: string) => {
|
||||
setRows((prev) =>
|
||||
prev.map((row) => {
|
||||
if (row.rowKey !== rowKey) return row;
|
||||
const { snapshot: _s, ...committed } = row;
|
||||
return { ...committed, isDirty: true };
|
||||
}),
|
||||
);
|
||||
setEditingRowKey(null);
|
||||
}, []);
|
||||
|
||||
const markDeleted = useCallback((rowKey: string) => {
|
||||
setRows((prev) => {
|
||||
const target = prev.find((r) => r.rowKey === rowKey);
|
||||
if (!target || !isPrgrsSttsRowEditable(target)) return prev;
|
||||
|
||||
if (target.isNew) {
|
||||
return prev.filter((r) => r.rowKey !== rowKey);
|
||||
}
|
||||
|
||||
return prev.map((row) =>
|
||||
row.rowKey === rowKey ? { ...row, isDeleted: true, isDirty: true } : row,
|
||||
);
|
||||
});
|
||||
setEditingRowKey((key) => (key === rowKey ? null : key));
|
||||
}, []);
|
||||
|
||||
const reorderRows = useCallback(
|
||||
(activeKey: string, overKey: string): ElaprPrgrsSttsRow[] => {
|
||||
if (isEditingLocked) return [];
|
||||
|
||||
let payloadRows: ElaprPrgrsSttsRow[] = [];
|
||||
|
||||
setRows((prev) => {
|
||||
const visible = getVisibleRows(prev);
|
||||
const activeRow = visible.find((r) => r.rowKey === activeKey);
|
||||
const overRow = visible.find((r) => r.rowKey === overKey);
|
||||
if (!activeRow || !overRow) return prev;
|
||||
if (!isPrgrsSttsRowEditable(activeRow) || !isPrgrsSttsRowEditable(overRow)) return prev;
|
||||
|
||||
const oldIndex = visible.findIndex((r) => r.rowKey === activeKey);
|
||||
const newIndex = visible.findIndex((r) => r.rowKey === overKey);
|
||||
if (oldIndex < 0 || newIndex < 0) return prev;
|
||||
|
||||
const reorderedVisible = assignPrgrsSttsAtrzSeqNumbers(
|
||||
arrayMove(visible, oldIndex, newIndex),
|
||||
).map((row) => (isPrgrsSttsRowEditable(row) ? { ...row, isDirty: true } : row));
|
||||
|
||||
const visibleKeys = new Set(reorderedVisible.map((r) => r.rowKey));
|
||||
const deletedRows = prev.filter((r) => r.isDeleted);
|
||||
const merged = [
|
||||
...reorderedVisible,
|
||||
...deletedRows.filter((r) => !visibleKeys.has(r.rowKey)),
|
||||
];
|
||||
|
||||
payloadRows = reorderedVisible.filter(isPrgrsSttsRowEditable);
|
||||
return merged;
|
||||
});
|
||||
|
||||
return payloadRows;
|
||||
},
|
||||
[isEditingLocked],
|
||||
);
|
||||
|
||||
return {
|
||||
rows,
|
||||
visibleRows,
|
||||
editingRowKey,
|
||||
isEditingLocked,
|
||||
addRow,
|
||||
startEdit,
|
||||
updateRow,
|
||||
setDcarbYn,
|
||||
cancelEdit,
|
||||
commitEdit,
|
||||
markDeleted,
|
||||
reorderRows,
|
||||
};
|
||||
}
|
||||
@@ -6,14 +6,14 @@ export const ELAPR_APLN_SAVE_MODE = {
|
||||
DELETE: "D",
|
||||
} as const;
|
||||
|
||||
export type ElaprAplnSaveMode =
|
||||
(typeof ELAPR_APLN_SAVE_MODE)[keyof typeof ELAPR_APLN_SAVE_MODE];
|
||||
export type ElaprAplnSaveMode = (typeof ELAPR_APLN_SAVE_MODE)[keyof typeof ELAPR_APLN_SAVE_MODE];
|
||||
|
||||
export interface ElaprApln {
|
||||
aplnId: string;
|
||||
elaprTmpltId: string;
|
||||
aprvrUuid?: string;
|
||||
aprvrNm?: string;
|
||||
aprvrUserId?: string;
|
||||
atrzSeComCd?: string;
|
||||
atrzSeComNm?: string;
|
||||
atrzSeqNo?: number;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { ElaprApln } from "@/entities/elapr";
|
||||
import type { ElaprApln } from "./elapr-apln-interface";
|
||||
|
||||
/** 그리드 로컬 행 — API 응답 + UI 보조 필드 */
|
||||
export interface ElaprAplnRow extends Omit<ElaprApln, "aplnId"> {
|
||||
@@ -1,22 +1,47 @@
|
||||
export interface ElaprDmndSaveRequest {
|
||||
import type { ElaprDocPrgrsClientResponseDto, PageableParams, PageResponse } from "@/shared/model";
|
||||
|
||||
import type { ElaprPrgrsSttsSaveRequestDto } from "./elapr-prgrs-stts-interface";
|
||||
|
||||
/** 백엔드 ElaprDmndSaveRequestDto */
|
||||
export interface ElaprDmndSaveRequestDto {
|
||||
elaprDocNo: string;
|
||||
elaprTmpltId: string;
|
||||
dmndTtl: string;
|
||||
dmndCn: string;
|
||||
elaprPrgrsSttsList?: ElaprPrgrsSttsSaveRequestDto[];
|
||||
}
|
||||
|
||||
export interface ElaprDmndResponse {
|
||||
/** 백엔드 ElaprDmndResponseDto */
|
||||
export interface ElaprDmndResponseDto {
|
||||
elaprDmndId: string;
|
||||
rqstrUuid?: string;
|
||||
elaprDocNo: string;
|
||||
elaprTmpltId: string;
|
||||
dmndTtl: string;
|
||||
dmndCn: string;
|
||||
atrzSttsComCd: string;
|
||||
rqstrUuid: string;
|
||||
regDt: string;
|
||||
updtDt: string;
|
||||
atrzSttsComNm?: string;
|
||||
dmndDt?: string;
|
||||
updtDt?: string;
|
||||
}
|
||||
|
||||
export interface ElaprDmndService {
|
||||
create(body: ElaprDmndSaveRequest): Promise<ElaprDmndResponse>;
|
||||
/** 백엔드 ElaprDmndSearchDto + Pageable */
|
||||
export interface ElaprDmndSearchDto extends PageableParams {
|
||||
rqstrUuid?: string;
|
||||
atrzSttsComCd?: string;
|
||||
dmndTtl?: string;
|
||||
dmndDtStart?: string;
|
||||
dmndDtEnd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprDocPrgrsResponseDto */
|
||||
export type ElaprDocPrgrsResponseDto = ElaprDocPrgrsClientResponseDto;
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface ElaprDmndService {
|
||||
findPage(params: ElaprDmndSearchDto): Promise<PageResponse<ElaprDmndResponseDto>>;
|
||||
findById(elaprDmndId: string): Promise<ElaprDmndResponseDto>;
|
||||
save(body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto>;
|
||||
update(elaprDmndId: string, body: ElaprDmndSaveRequestDto): Promise<ElaprDmndResponseDto>;
|
||||
updateStatus(elaprDmndId: string, atrzSttsComCd: string): Promise<ElaprDmndResponseDto>;
|
||||
remove(elaprDmndId: string): Promise<void>;
|
||||
findDocPrgrsByDocNo(elaprDocNo: string): Promise<ElaprDocPrgrsResponseDto[]>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { PageableParams, PageResponse } from "@/shared/model";
|
||||
|
||||
export const ELAPR_PRGRS_STTS_SAVE_MODE = {
|
||||
CREATE: "C",
|
||||
READ: "R",
|
||||
UPDATE: "U",
|
||||
DELETE: "D",
|
||||
} as const;
|
||||
|
||||
export type ElaprPrgrsSttsSaveMode =
|
||||
(typeof ELAPR_PRGRS_STTS_SAVE_MODE)[keyof typeof ELAPR_PRGRS_STTS_SAVE_MODE];
|
||||
|
||||
/** 백엔드 ElaprPrgrsSttsResponseDto */
|
||||
export interface ElaprPrgrsSttsResponseDto {
|
||||
elaprPrgrsId: string;
|
||||
elaprDmndId?: string;
|
||||
aprvrUuid?: string;
|
||||
atrzSeComCd?: string;
|
||||
atrzSeComNm?: string;
|
||||
atrzSeqNo?: number;
|
||||
atrzPrgrsSttsComCd?: string;
|
||||
atrzPrgrsSttsComNm?: string;
|
||||
rjctRsn?: string;
|
||||
atrzDt?: string;
|
||||
dcarbYn?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprPrgrsSttsSaveRequestDto */
|
||||
export interface ElaprPrgrsSttsSaveRequestDto {
|
||||
mode?: ElaprPrgrsSttsSaveMode;
|
||||
elaprPrgrsId?: string;
|
||||
elaprDmndId?: string;
|
||||
aprvrUuid?: string;
|
||||
atrzSeComCd?: string;
|
||||
atrzSeqNo?: number;
|
||||
atrzPrgrsSttsComCd?: string;
|
||||
dcarbYn?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprPrgrsSttsSearchDto */
|
||||
export interface ElaprPrgrsSttsSearchDto {
|
||||
atrzSeComCd: string;
|
||||
atrzSeqNo: number;
|
||||
atrzPrgrsSttsComCd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprPrgrsSttsSearchDto + Pageable */
|
||||
export interface ElaprPrgrsSttsMyDocSearchParams extends ElaprPrgrsSttsSearchDto, PageableParams {}
|
||||
|
||||
/** 백엔드 ElaprProcessRequestDto */
|
||||
export interface ElaprProcessRequestDto {
|
||||
atrzPrgrsSttsComCd: string;
|
||||
rjctRsn?: string;
|
||||
finalApprovalYn?: boolean;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprApproverChangeRequestDto */
|
||||
export interface ElaprApproverChangeRequestDto {
|
||||
aprvrUuid: string;
|
||||
}
|
||||
|
||||
/** 백엔드 ElaprApproverInsertRequestDto */
|
||||
export interface ElaprApproverInsertRequestDto {
|
||||
aprvrUuid: string;
|
||||
atrzSeComCd: string;
|
||||
atrzSeqNo: number;
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface ElaprPrgrsSttsService {
|
||||
findAllByDmndId(elaprDmndId?: string): Promise<ElaprPrgrsSttsResponseDto[]>;
|
||||
findById(elaprPrgrsId: string): Promise<ElaprPrgrsSttsResponseDto>;
|
||||
save(requestDtos: ElaprPrgrsSttsSaveRequestDto[]): Promise<ElaprPrgrsSttsResponseDto[]>;
|
||||
process(elaprPrgrsId: string, body: ElaprProcessRequestDto): Promise<ElaprPrgrsSttsResponseDto>;
|
||||
processByDocNoAndAprvrUuid(
|
||||
elaprDocNo: string,
|
||||
aprvrUuid: string,
|
||||
body: ElaprProcessRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto>;
|
||||
delete(elaprPrgrsId: string): Promise<void>;
|
||||
updateApprover(
|
||||
elaprPrgrsId: string,
|
||||
body: ElaprApproverChangeRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto>;
|
||||
insertApprover(
|
||||
elaprDmndId: string,
|
||||
body: ElaprApproverInsertRequestDto,
|
||||
): Promise<ElaprPrgrsSttsResponseDto>;
|
||||
findMyElaperDocList(
|
||||
params: ElaprPrgrsSttsMyDocSearchParams,
|
||||
): Promise<PageResponse<ElaprPrgrsSttsResponseDto>>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface ReviewerOption {
|
||||
uuid: string;
|
||||
name: string;
|
||||
deptNm: string;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
|
||||
import { RvwMnpwMngResponseDto, useRvwMnpwMngPage } from "@/entities/rvw-mnpw";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
|
||||
import { type ReviewerOption } from "../model/reviewer-option";
|
||||
|
||||
const PAGE_SIZE = 500;
|
||||
|
||||
interface AprvrPickerDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
atrzSeComCd: string;
|
||||
onSelect: (reviewer: ReviewerOption) => void;
|
||||
}
|
||||
|
||||
function AprvrPickerDialog({ open, onOpenChange, atrzSeComCd, onSelect }: AprvrPickerDialogProps) {
|
||||
const { data, isPending, isError } = useRvwMnpwMngPage(
|
||||
{
|
||||
atrzSeComCd,
|
||||
page: 0,
|
||||
size: PAGE_SIZE,
|
||||
sort: "userUuidNm,asc",
|
||||
},
|
||||
{ enabled: open },
|
||||
);
|
||||
|
||||
const items = data?.content ?? [];
|
||||
|
||||
function fromRvwMnpwMng(item: RvwMnpwMngResponseDto): ReviewerOption {
|
||||
return {
|
||||
uuid: item.userUuid,
|
||||
name: item.userUuidNm ?? "",
|
||||
deptNm: item.ognzNm ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
function handleSelect(item: (typeof items)[number]) {
|
||||
onSelect(fromRvwMnpwMng(item));
|
||||
onOpenChange(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[56rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검토인력 선택</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="h-[36rem] overflow-auto">
|
||||
{isPending ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[16rem]" />
|
||||
</div>
|
||||
) : isError ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
검토인력 목록을 불러오지 못했습니다.
|
||||
</p>
|
||||
) : items.length === 0 ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
배치된 검토인력이 없습니다.
|
||||
</p>
|
||||
) : (
|
||||
<Table className="w-full">
|
||||
<colgroup>
|
||||
<col width="25%" />
|
||||
<col width="25%" />
|
||||
<col width="15%" />
|
||||
<col width="15%" />
|
||||
<col width="20%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>성명</TableHead>
|
||||
<TableHead>부서</TableHead>
|
||||
<TableHead>직위</TableHead>
|
||||
<TableHead>직급</TableHead>
|
||||
<TableHead>선택</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((item) => (
|
||||
<TableRow key={`${item.atrzSeComCd}-${item.ognzSn}-${item.userUuid}`}>
|
||||
<TableCell align="center">{item.userUuidNm || item.userUuid}</TableCell>
|
||||
<TableCell align="center">{item.ognzNm || "-"}</TableCell>
|
||||
<TableCell align="center">{item.jbpsSeNm || "-"}</TableCell>
|
||||
<TableCell align="center">{item.jbgdSeNm || "-"}</TableCell>
|
||||
<TableCell align="center">
|
||||
<Button type="button" size="sm" onClick={() => handleSelect(item)}>
|
||||
선택
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" color="white" onClick={() => onOpenChange(false)}>
|
||||
닫기
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { AprvrPickerDialog };
|
||||
@@ -0,0 +1,202 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core";
|
||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import {
|
||||
Button,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
|
||||
import { useElaprAplnPage } from "../api/queries";
|
||||
import { validateAplnRow } from "../lib/apln-validators";
|
||||
import { useAplnRows } from "../lib/use-apln-rows";
|
||||
import { useDndSensors } from "../lib/use-dnd-sensors";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
import type { ReviewerOption } from "../model/reviewer-option";
|
||||
|
||||
import { AprvrPickerDialog } from "./aprvr-picker-dialog";
|
||||
import { SortableAplnPickerRow } from "./sortable-apln-picker-row";
|
||||
|
||||
interface ElaprAplnPickerGridProps {
|
||||
elaprTmpltId: string | null;
|
||||
onRowsChange?: (rows: ElaprAplnRow[]) => void;
|
||||
onEditingLockedChange?: (locked: boolean) => void;
|
||||
}
|
||||
|
||||
function ElaprAplnPickerGrid({
|
||||
elaprTmpltId,
|
||||
onRowsChange,
|
||||
onEditingLockedChange,
|
||||
}: ElaprAplnPickerGridProps) {
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [pickingRowKey, setPickingRowKey] = useState<string | null>(null);
|
||||
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
const aplnQuery = useElaprAplnPage({
|
||||
elaprTmpltId: elaprTmpltId ?? undefined,
|
||||
page: 0,
|
||||
size: 200,
|
||||
sort: "atrzSeqNo,asc",
|
||||
});
|
||||
|
||||
const {
|
||||
rows,
|
||||
visibleRows,
|
||||
editingRowKey,
|
||||
isEditingLocked,
|
||||
addRow,
|
||||
startEdit,
|
||||
updateRow,
|
||||
cancelEdit,
|
||||
commitEdit,
|
||||
markDeleted,
|
||||
reorderRows,
|
||||
} = useAplnRows({
|
||||
elaprTmpltId,
|
||||
serverRows: aplnQuery.data?.content,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
onRowsChange?.(rows);
|
||||
}, [rows, onRowsChange]);
|
||||
|
||||
useEffect(() => {
|
||||
onEditingLockedChange?.(isEditingLocked);
|
||||
}, [isEditingLocked, onEditingLockedChange]);
|
||||
|
||||
const sensors = useDndSensors();
|
||||
const isDisabled = !elaprTmpltId;
|
||||
const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined;
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
if (!over || active.id === over.id) return;
|
||||
|
||||
reorderRows(String(active.id), String(over.id));
|
||||
}
|
||||
|
||||
function handleRowChange(row: ElaprAplnRow, patch: Partial<ElaprAplnRow>) {
|
||||
updateRow(row.rowKey, patch);
|
||||
}
|
||||
|
||||
function handleRowSave(row: ElaprAplnRow) {
|
||||
const fieldError = validateAplnRow(row);
|
||||
if (fieldError) {
|
||||
alert(fieldError);
|
||||
return;
|
||||
}
|
||||
|
||||
commitEdit(row.rowKey);
|
||||
}
|
||||
|
||||
function handleRowDelete(row: ElaprAplnRow) {
|
||||
markDeleted(row.rowKey);
|
||||
}
|
||||
|
||||
function handleReviewerSelect(reviewer: ReviewerOption) {
|
||||
if (!pickingRowKey) return;
|
||||
updateRow(pickingRowKey, {
|
||||
aprvrUuid: reviewer.uuid,
|
||||
aprvrNm: reviewer.name,
|
||||
});
|
||||
setPickingRowKey(null);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 min-h-[32rem] flex-1 pt-0">
|
||||
<div className="card-header flex flex-wrap items-center justify-between gap-3">
|
||||
<h3 className="text-2xl font-semibold">결재선 관리</h3>
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
color="green"
|
||||
disabled={isDisabled || isEditingLocked}
|
||||
onClick={addRow}
|
||||
>
|
||||
결재선 추가
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{aplnQuery.isPending && elaprTmpltId ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[16rem]" />
|
||||
</div>
|
||||
) : !elaprTmpltId ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">결재서식을 선택하세요.</p>
|
||||
) : visibleRows.length === 0 ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요.
|
||||
</p>
|
||||
) : (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<Table className="w-full">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
<col width="50%" />
|
||||
<col width="20%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-10" />
|
||||
<TableHead>결재순서</TableHead>
|
||||
<TableHead>결재구분</TableHead>
|
||||
<TableHead>결재자</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<SortableContext
|
||||
items={visibleRows.map((r) => r.rowKey)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{visibleRows.map((row, index) => (
|
||||
<SortableAplnPickerRow
|
||||
key={row.rowKey}
|
||||
row={row}
|
||||
displaySeqNo={index + 1}
|
||||
isEditing={editingRowKey === row.rowKey}
|
||||
isLocked={isEditingLocked}
|
||||
dragDisabled={isEditingLocked || !row.atrzSeComCd}
|
||||
onStartEdit={() => startEdit(row.rowKey)}
|
||||
onSave={() => handleRowSave(row)}
|
||||
onCancel={cancelEdit}
|
||||
onDelete={() => handleRowDelete(row)}
|
||||
onChange={(patch) => handleRowChange(row, patch)}
|
||||
onPickAprvr={() => {
|
||||
setPickingRowKey(row.rowKey);
|
||||
setPickerOpen(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</DndContext>
|
||||
)}
|
||||
|
||||
<AprvrPickerDialog
|
||||
open={pickerOpen}
|
||||
onOpenChange={setPickerOpen}
|
||||
atrzSeComCd={pickingRow?.atrzSeComCd ?? ""}
|
||||
onSelect={handleReviewerSelect}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprAplnPickerGrid };
|
||||
@@ -0,0 +1,408 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core";
|
||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
|
||||
import { elaprPrgrsSttsService } from "../api/elapr-prgrs-stts-service";
|
||||
import { useElaprDocPrgrsByDocNo, useSaveElaprPrgrsStts } from "../api/queries";
|
||||
import { validateAtrzOrder } from "../lib/apln-validators";
|
||||
import { findCurrentUserPendingApprovalRequest } from "../lib/approval-request-utils";
|
||||
import {
|
||||
buildPrgrsSttsSavePayload,
|
||||
hasUnsavedPrgrsSttsChanges,
|
||||
isPrgrsSttsRowEditable,
|
||||
validateAllPrgrsSttsRows,
|
||||
validatePrgrsSttsRow,
|
||||
} from "../lib/prgrs-stts-save-utils";
|
||||
import { useDndSensors } from "../lib/use-dnd-sensors";
|
||||
import { usePrgrsSttsRows } from "../lib/use-prgrs-stts-rows";
|
||||
import type { ReviewerOption } from "../model/reviewer-option";
|
||||
|
||||
import { AprvrPickerDialog } from "./aprvr-picker-dialog";
|
||||
import { SortablePrgrsSttsRow } from "./sortable-prgrs-stts-row";
|
||||
|
||||
interface ElaprApprovalDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
elaprDocNo: string;
|
||||
currentUserUuid: string;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
const UNSAVED_PRGRS_STTS_MESSAGE = "결재선 변경 이력이 있습니다.\n결재선 저장을 해 주세요.";
|
||||
|
||||
function ElaprApprovalDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
elaprDocNo,
|
||||
currentUserUuid,
|
||||
onSuccess,
|
||||
}: ElaprApprovalDialogProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [rjctRsn, setRjctRsn] = useState("");
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [pickingRowKey, setPickingRowKey] = useState<string | null>(null);
|
||||
|
||||
const { data: elaprDocPrgrsList, isLoading } = useElaprDocPrgrsByDocNo(open ? elaprDocNo : null);
|
||||
|
||||
const elaprDmndId = elaprDocPrgrsList?.[0]?.elaprDmndId;
|
||||
|
||||
const {
|
||||
rows,
|
||||
visibleRows,
|
||||
editingRowKey,
|
||||
isEditingLocked,
|
||||
addRow,
|
||||
startEdit,
|
||||
updateRow,
|
||||
setDcarbYn,
|
||||
cancelEdit,
|
||||
commitEdit,
|
||||
markDeleted,
|
||||
reorderRows,
|
||||
} = usePrgrsSttsRows({
|
||||
serverRows: elaprDocPrgrsList,
|
||||
elaprDmndId,
|
||||
});
|
||||
|
||||
const savePrgrsSttsMutation = useSaveElaprPrgrsStts();
|
||||
const sensors = useDndSensors();
|
||||
|
||||
const currentUserPrgrs = findCurrentUserPendingApprovalRequest(
|
||||
elaprDocPrgrsList,
|
||||
currentUserUuid,
|
||||
);
|
||||
const canApprove = Boolean(currentUserPrgrs?.elaprPrgrsId);
|
||||
|
||||
const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined;
|
||||
const isBusy = isProcessing || savePrgrsSttsMutation.isPending;
|
||||
const isPrgrsSttsUnsaved = hasUnsavedPrgrsSttsChanges(rows, elaprDocPrgrsList ?? []);
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
if (!over || active.id === over.id) return;
|
||||
|
||||
reorderRows(String(active.id), String(over.id));
|
||||
}
|
||||
|
||||
function handleRowSave(rowKey: string) {
|
||||
const row = rows.find((r) => r.rowKey === rowKey);
|
||||
if (!row) return;
|
||||
|
||||
const fieldError = validatePrgrsSttsRow(row);
|
||||
if (fieldError) {
|
||||
alert(fieldError);
|
||||
return;
|
||||
}
|
||||
|
||||
commitEdit(rowKey);
|
||||
}
|
||||
|
||||
function handleReviewerSelect(reviewer: ReviewerOption) {
|
||||
if (!pickingRowKey) return;
|
||||
updateRow(pickingRowKey, {
|
||||
aprvrUuid: reviewer.uuid,
|
||||
aprvrNm: reviewer.name,
|
||||
ognzNm: reviewer.deptNm,
|
||||
});
|
||||
setPickingRowKey(null);
|
||||
}
|
||||
|
||||
function assertPrgrsSttsSaved(): boolean {
|
||||
if (isEditingLocked) {
|
||||
alert(UNSAVED_PRGRS_STTS_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPrgrsSttsUnsaved) {
|
||||
alert(UNSAVED_PRGRS_STTS_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSavePrgrsStts() {
|
||||
if (!elaprDmndId) {
|
||||
alert("결재 요청 정보가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const validationError = validateAllPrgrsSttsRows(rows, isEditingLocked);
|
||||
if (validationError) {
|
||||
alert(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
const orderError = validateAtrzOrder(rows);
|
||||
if (orderError) {
|
||||
alert(orderError);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildPrgrsSttsSavePayload(rows, elaprDmndId, elaprDocPrgrsList ?? []);
|
||||
if (payload.length === 0) {
|
||||
alert("변경된 결재선이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
await savePrgrsSttsMutation.mutateAsync(payload);
|
||||
alert("결재선이 저장되었습니다.");
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
});
|
||||
onSuccess();
|
||||
} catch (error: unknown) {
|
||||
let errorMessage = "결재선 저장 중 오류가 발생했습니다.";
|
||||
if (error instanceof AxiosError) {
|
||||
errorMessage = error.response?.data?.message ?? errorMessage;
|
||||
}
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleApprove() {
|
||||
if (!currentUserPrgrs?.elaprPrgrsId) return;
|
||||
if (!assertPrgrsSttsSaved()) return;
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
await elaprPrgrsSttsService.process(currentUserPrgrs.elaprPrgrsId, {
|
||||
atrzPrgrsSttsComCd: "ATRZ_PRGRS_003",
|
||||
});
|
||||
alert("결재가 완료되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
});
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
let errorMessage = "";
|
||||
if (error instanceof AxiosError) {
|
||||
errorMessage = error.response?.data?.message ?? "";
|
||||
} else {
|
||||
errorMessage = "결재 처리 중 오류가 발생했습니다.";
|
||||
}
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReject() {
|
||||
if (!currentUserPrgrs?.elaprPrgrsId) return;
|
||||
if (!rjctRsn.trim()) {
|
||||
alert("반려 사유를 입력해주세요.");
|
||||
return;
|
||||
}
|
||||
if (!assertPrgrsSttsSaved()) return;
|
||||
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
await elaprPrgrsSttsService.process(currentUserPrgrs.elaprPrgrsId, {
|
||||
atrzPrgrsSttsComCd: "ATRZ_PRGRS_004",
|
||||
rjctRsn: rjctRsn.trim(),
|
||||
});
|
||||
alert("반려 처리가 완료되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", elaprDocNo],
|
||||
});
|
||||
onSuccess();
|
||||
onOpenChange(false);
|
||||
} catch (error: unknown) {
|
||||
let errorMessage = "";
|
||||
if (error instanceof AxiosError) {
|
||||
errorMessage = error.response?.data?.message ?? "";
|
||||
} else {
|
||||
errorMessage = "반려 처리 중 오류가 발생했습니다.";
|
||||
}
|
||||
alert(errorMessage);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setRjctRsn("");
|
||||
setPickingRowKey(null);
|
||||
onOpenChange(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-[72rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>전자결재</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<div className="mb-3 flex flex-wrap items-center justify-between gap-3">
|
||||
<h4 className="text-xl font-semibold">결재 진행 단계</h4>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
color="white"
|
||||
disabled={!elaprDmndId || isEditingLocked || isBusy}
|
||||
onClick={addRow}
|
||||
>
|
||||
결재선 추가
|
||||
</Button>
|
||||
{elaprDmndId && (
|
||||
<Button
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={handleSavePrgrsStts}
|
||||
disabled={!isPrgrsSttsUnsaved || isEditingLocked || isBusy}
|
||||
>
|
||||
결재선 저장
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-12 w-full" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
</div>
|
||||
) : visibleRows.length === 0 ? (
|
||||
<div className="py-8 text-center text-gray-500">
|
||||
{elaprDmndId
|
||||
? "등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요."
|
||||
: "결재 정보가 없습니다."}
|
||||
</div>
|
||||
) : (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<Table className="w-full">
|
||||
<colgroup>
|
||||
<col width="8%" />
|
||||
<col width="8%" />
|
||||
<col width="10%" />
|
||||
<col width="30%" />
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
<col width="24%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-10" />
|
||||
<TableHead>결재순서</TableHead>
|
||||
<TableHead>결재구분</TableHead>
|
||||
<TableHead>결재자</TableHead>
|
||||
<TableHead>전결자 지정</TableHead>
|
||||
<TableHead>결재상태</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<SortableContext
|
||||
items={visibleRows.map((r) => r.rowKey)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{visibleRows.map((row, index) => (
|
||||
<SortablePrgrsSttsRow
|
||||
key={row.rowKey}
|
||||
row={row}
|
||||
displaySeqNo={index + 1}
|
||||
isEditing={editingRowKey === row.rowKey}
|
||||
isLocked={isEditingLocked}
|
||||
dragDisabled={
|
||||
isEditingLocked || !isPrgrsSttsRowEditable(row) || !row.atrzSeComCd
|
||||
}
|
||||
onStartEdit={() => startEdit(row.rowKey)}
|
||||
onSave={() => handleRowSave(row.rowKey)}
|
||||
onCancel={cancelEdit}
|
||||
onDelete={() => markDeleted(row.rowKey)}
|
||||
onChange={(patch) => updateRow(row.rowKey, patch)}
|
||||
onPickAprvr={() => {
|
||||
setPickingRowKey(row.rowKey);
|
||||
setPickerOpen(true);
|
||||
}}
|
||||
onToggleDcarb={(checked) => setDcarbYn(row.rowKey, checked)}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</DndContext>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{canApprove && (
|
||||
<div>
|
||||
<h4 className="mb-3 text-xl font-semibold">반려 의견</h4>
|
||||
<Textarea
|
||||
value={rjctRsn}
|
||||
onChange={(event) => setRjctRsn(event.target.value)}
|
||||
placeholder="반려 시 사유를 입력하세요"
|
||||
rows={4}
|
||||
className="!text-xl"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Button type="button" color="white" onClick={handleClose} disabled={isBusy}>
|
||||
취소
|
||||
</Button>
|
||||
{canApprove && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button onClick={handleApprove} disabled={isBusy}>
|
||||
결재
|
||||
</Button>
|
||||
<Button color="red" onClick={handleReject} disabled={isBusy}>
|
||||
반려
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
<AprvrPickerDialog
|
||||
open={pickerOpen}
|
||||
onOpenChange={setPickerOpen}
|
||||
atrzSeComCd={pickingRow?.atrzSeComCd ?? ""}
|
||||
onSelect={handleReviewerSelect}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprApprovalDialog };
|
||||
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { ChevronRight } from "lucide-react";
|
||||
|
||||
import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
import { cn, Skeleton } from "@common/components";
|
||||
|
||||
import { useElaprDocPrgrsByDocNo } from "../api/queries";
|
||||
import { getApproverStatus, sortByAtrzSeqNo } from "../lib/approval-utils";
|
||||
|
||||
const REJECT_STATUS_CD = "ATRZ_PRGRS_004";
|
||||
|
||||
interface ElaprDocPrgrsStatusProps {
|
||||
elaprDocNo: string | null | undefined;
|
||||
}
|
||||
|
||||
function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) {
|
||||
const status = getApproverStatus(item.atrzPrgrsSttsComCd);
|
||||
const isRejected = item.atrzPrgrsSttsComCd === REJECT_STATUS_CD;
|
||||
const rjctRsn = item.rjctRsn?.trim();
|
||||
const metaParts = [item.ognzNm, item.jbpsSeNm].filter(Boolean);
|
||||
|
||||
const statusBadge = (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-sm px-2 py-1 text-lg font-medium",
|
||||
status.badgeClass,
|
||||
isRejected && rjctRsn && "cursor-help underline decoration-dotted underline-offset-4",
|
||||
)}
|
||||
>
|
||||
{status.label}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-w-[12rem] flex-1 flex-col items-center gap-3 p-4 text-center">
|
||||
{isRejected && rjctRsn ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{statusBadge}</TooltipTrigger>
|
||||
<TooltipContent sideOffset={4} className="max-w-md whitespace-pre-wrap">
|
||||
{rjctRsn}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
statusBadge
|
||||
)}
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl text-muted-foreground">[{item.atrzSeComNm || "-"}]</span>
|
||||
<span className="text-xl font-semibold">{item.aprvrNm || item.aprvrUuid || "-"}</span>
|
||||
</div>
|
||||
{metaParts.length > 0 && (
|
||||
<span className="text-base text-gray-500">{metaParts.join(" · ")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) {
|
||||
const { data, isLoading } = useElaprDocPrgrsByDocNo(elaprDocNo);
|
||||
const steps = sortByAtrzSeqNo(data ?? []);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 px-4 py-6">
|
||||
<Skeleton className="h-24 flex-1 rounded-sm" />
|
||||
<Skeleton className="h-24 flex-1 rounded-sm" />
|
||||
<Skeleton className="h-24 flex-1 rounded-sm" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (steps.length === 0) {
|
||||
return <div className="py-8 text-center text-xl text-gray-500">결재 정보가 없습니다.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-stretch justify-start gap-4 overflow-x-auto">
|
||||
{steps.map((item, index) => (
|
||||
<Fragment key={item.elaprPrgrsId ?? `${item.atrzSeqNo}-${item.aprvrUuid ?? index}`}>
|
||||
<div className="flex items-center">
|
||||
{index > 0 && <ChevronRight className="size-8 text-muted-foreground" />}
|
||||
</div>
|
||||
<ElaprDocPrgrsStep item={item} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprDocPrgrsStatus };
|
||||
@@ -0,0 +1,166 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@common/components";
|
||||
|
||||
import { useElaprTmpltPage } from "../api/queries";
|
||||
import {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAtrzOrder,
|
||||
validateUniqueSeqNo,
|
||||
} from "../lib/apln-validators";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
import type { ElaprTmplt } from "../model/elapr-tmplt-interface";
|
||||
|
||||
import { ElaprAplnPickerGrid } from "./elapr-apln-picker-grid";
|
||||
import { ElaprTmpltSelectTable } from "./elapr-tmplt-select-table";
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
interface ElaprTmpltPickerConfirmPayload {
|
||||
tmplt: ElaprTmplt;
|
||||
aplnRows: ElaprAplnRow[];
|
||||
}
|
||||
|
||||
interface ElaprTmpltPickerDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
docSeComCd: string;
|
||||
onSelect?: (tmplt: ElaprTmplt) => void;
|
||||
onConfirm?: (payload: ElaprTmpltPickerConfirmPayload) => void;
|
||||
}
|
||||
|
||||
function ElaprTmpltPickerDialog({
|
||||
open,
|
||||
onClose,
|
||||
docSeComCd,
|
||||
onSelect,
|
||||
onConfirm,
|
||||
}: ElaprTmpltPickerDialogProps) {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const [selectedTmplt, setSelectedTmplt] = useState<ElaprTmplt | null>(null);
|
||||
const [aplnRows, setAplnRows] = useState<ElaprAplnRow[]>([]);
|
||||
const [isEditingLocked, setIsEditingLocked] = useState(false);
|
||||
|
||||
const tmpltQuery = useElaprTmpltPage({
|
||||
page: 0,
|
||||
size: PAGE_SIZE,
|
||||
sort: "regDt,desc",
|
||||
docSeComCd,
|
||||
});
|
||||
|
||||
const handleRowsChange = useCallback((rows: ElaprAplnRow[]) => {
|
||||
setAplnRows(rows);
|
||||
}, []);
|
||||
|
||||
const handleEditingLockedChange = useCallback((locked: boolean) => {
|
||||
setIsEditingLocked(locked);
|
||||
}, []);
|
||||
|
||||
function handleSelectTmplt(tmplt: ElaprTmplt | null) {
|
||||
setSelectedTmplt(tmplt);
|
||||
setAplnRows([]);
|
||||
setIsEditingLocked(false);
|
||||
}
|
||||
|
||||
function handleOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
setSelectedTmplt(null);
|
||||
setAplnRows([]);
|
||||
setIsEditingLocked(false);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
if (!selectedTmplt) {
|
||||
alert("결재서식을 선택해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const rowsError = validateAllRowsForSave(aplnRows, isEditingLocked);
|
||||
if (rowsError) {
|
||||
alert(rowsError);
|
||||
return;
|
||||
}
|
||||
|
||||
const orderError = validateAtrzOrder(aplnRows);
|
||||
if (orderError) {
|
||||
alert(orderError);
|
||||
return;
|
||||
}
|
||||
|
||||
const seqError = validateUniqueSeqNo(aplnRows);
|
||||
if (seqError) {
|
||||
alert(seqError);
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(aplnRows);
|
||||
|
||||
const payload: ElaprTmpltPickerConfirmPayload = {
|
||||
tmplt: selectedTmplt,
|
||||
aplnRows: visibleRows,
|
||||
};
|
||||
|
||||
if (onConfirm) {
|
||||
onConfirm(payload);
|
||||
} else {
|
||||
onSelect?.(selectedTmplt);
|
||||
}
|
||||
|
||||
setSelectedTmplt(null);
|
||||
setAplnRows([]);
|
||||
setIsEditingLocked(false);
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="max-w-[120rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>결재선 선택</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex gap-6">
|
||||
<ElaprTmpltSelectTable
|
||||
query={tmpltQuery}
|
||||
selectedTmpltId={selectedTmplt?.elaprTmpltId ?? null}
|
||||
onSelectTmplt={handleSelectTmplt}
|
||||
/>
|
||||
<ElaprAplnPickerGrid
|
||||
elaprTmpltId={selectedTmplt?.elaprTmpltId ?? null}
|
||||
onRowsChange={handleRowsChange}
|
||||
onEditingLockedChange={handleEditingLockedChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" color="white" onClick={() => handleOpenChange(false)}>
|
||||
닫기
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!selectedTmplt || isEditingLocked}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
확인
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprTmpltPickerDialog };
|
||||
export type { ElaprTmpltPickerConfirmPayload };
|
||||
@@ -0,0 +1,97 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import type { UseQueryResult } from "@tanstack/react-query";
|
||||
import { createColumnHelper, getCoreRowModel, type RowSelectionState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import type { ElaprTmplt } from "../model/elapr-tmplt-interface";
|
||||
|
||||
const columnHelper = createColumnHelper<ElaprTmplt>();
|
||||
|
||||
interface ElaprTmpltSelectTableProps {
|
||||
query: UseQueryResult<PageResponse<ElaprTmplt>>;
|
||||
selectedTmpltId: string | null;
|
||||
onSelectTmplt: (tmplt: ElaprTmplt | null) => void;
|
||||
}
|
||||
|
||||
function ElaprTmpltSelectTable({
|
||||
query,
|
||||
selectedTmpltId,
|
||||
onSelectTmplt,
|
||||
}: ElaprTmpltSelectTableProps) {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedTmpltId) {
|
||||
setRowSelection({ [selectedTmpltId]: true });
|
||||
} else {
|
||||
setRowSelection({});
|
||||
}
|
||||
}, [selectedTmpltId]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("elaprTmpltNm", {
|
||||
header: "결재서식명",
|
||||
meta: { align: "left" },
|
||||
}),
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: { rowSelection },
|
||||
enableRowSelection: true,
|
||||
enableMultiRowSelection: false,
|
||||
getRowId: (row) => row.elaprTmpltId,
|
||||
onRowSelectionChange: (updater) => {
|
||||
const next = typeof updater === "function" ? updater(rowSelection) : updater;
|
||||
setRowSelection(next);
|
||||
const selectedId = Object.keys(next)[0];
|
||||
if (!selectedId) {
|
||||
onSelectTmplt(null);
|
||||
return;
|
||||
}
|
||||
const selected = query.data?.content.find((t) => t.elaprTmpltId === selectedId);
|
||||
onSelectTmplt(selected ?? null);
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
onRowClick: (row) => {
|
||||
setRowSelection({ [row.elaprTmpltId]: true });
|
||||
onSelectTmplt(row);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 min-h-[32rem] flex-[0.5] bg-background pt-0">
|
||||
<div className="card-header h-[4.6rem]">
|
||||
<h3 className="text-2xl font-semibold">전자결재서식</h3>
|
||||
</div>
|
||||
{query.isPending && (
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[16rem]" />
|
||||
</div>
|
||||
)}
|
||||
{query.isError && (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
결재서식 목록을 불러오지 못했습니다.
|
||||
</p>
|
||||
)}
|
||||
{!query.isPending && !query.isError && tableComponent}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { ElaprTmpltSelectTable };
|
||||
@@ -0,0 +1,153 @@
|
||||
"use client";
|
||||
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { MoveVertical } from "lucide-react";
|
||||
|
||||
import { findCodeLabel, useCodeList } from "@/entities/code";
|
||||
import {
|
||||
Button,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
|
||||
interface SortableAplnPickerRowProps {
|
||||
row: ElaprAplnRow;
|
||||
displaySeqNo: number;
|
||||
isEditing: boolean;
|
||||
isLocked: boolean;
|
||||
dragDisabled: boolean;
|
||||
onStartEdit: () => void;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
onDelete: () => void;
|
||||
onChange: (patch: Partial<ElaprAplnRow>) => void;
|
||||
onPickAprvr: () => void;
|
||||
}
|
||||
|
||||
function SortableAplnPickerRow({
|
||||
row,
|
||||
displaySeqNo,
|
||||
isEditing,
|
||||
isLocked,
|
||||
dragDisabled,
|
||||
onStartEdit,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDelete,
|
||||
onChange,
|
||||
onPickAprvr,
|
||||
}: SortableAplnPickerRowProps) {
|
||||
const { data: atrzSeCodes } = useCodeList("ATRZSE");
|
||||
const selectableAtrzSeCodes = atrzSeCodes?.filter((code) => code.lwpoCd !== "ATRZ01");
|
||||
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: row.rowKey,
|
||||
disabled: dragDisabled,
|
||||
});
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.6 : 1,
|
||||
};
|
||||
|
||||
const rowDisabled = isLocked && !isEditing;
|
||||
const atrzSeLabel = row.atrzSeComNm || findCodeLabel(atrzSeCodes, row.atrzSeComCd);
|
||||
|
||||
return (
|
||||
<TableRow ref={setNodeRef} style={style} className={isEditing ? "bg-accent/30" : undefined}>
|
||||
<TableCell align="center" className="w-10">
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
className="cursor-grab !bg-transparent text-muted-foreground disabled:cursor-not-allowed disabled:opacity-40"
|
||||
disabled={dragDisabled}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
aria-label="순서 변경"
|
||||
>
|
||||
<MoveVertical className="!size-6" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
<TableCell align="center">{displaySeqNo}</TableCell>
|
||||
<TableCell align="center">
|
||||
{isEditing ? (
|
||||
<Select
|
||||
value={row.atrzSeComCd ?? ""}
|
||||
onValueChange={(v) =>
|
||||
onChange({ atrzSeComCd: v, atrzSeComNm: findCodeLabel(selectableAtrzSeCodes, v) })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="min-w-[120px]">
|
||||
<SelectValue placeholder="결재구분" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{selectableAtrzSeCodes?.map((code) => (
|
||||
<SelectItem key={code.lwpoCd} value={code.lwpoCd}>
|
||||
{code.lwpoCdNm}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
atrzSeLabel || "-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span>{row.aprvrNm || row.aprvrUuid || ""}</span>
|
||||
{isEditing && (
|
||||
<Button type="button" size="sm" onClick={onPickAprvr} disabled={!row.atrzSeComCd}>
|
||||
선택
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<div className="flex justify-center gap-2">
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Button type="button" size="sm" onClick={onSave}>
|
||||
확인
|
||||
</Button>
|
||||
<Button type="button" size="sm" color="white" onClick={onCancel}>
|
||||
취소
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="white"
|
||||
disabled={rowDisabled}
|
||||
onClick={onStartEdit}
|
||||
>
|
||||
수정
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="gray"
|
||||
disabled={rowDisabled}
|
||||
onClick={onDelete}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export { SortableAplnPickerRow };
|
||||
@@ -0,0 +1,212 @@
|
||||
"use client";
|
||||
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { MoveVertical } from "lucide-react";
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
import { findCodeLabel, useCodeList } from "@/entities/code";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
cn,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
TableCell,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
|
||||
import { getApproverStatus } from "../lib/approval-utils";
|
||||
import {
|
||||
type ElaprPrgrsSttsRow,
|
||||
isPrgrsSttsRowDcarbable,
|
||||
isPrgrsSttsRowEditable,
|
||||
} from "../lib/prgrs-stts-save-utils";
|
||||
|
||||
interface SortablePrgrsSttsRowProps {
|
||||
row: ElaprPrgrsSttsRow;
|
||||
displaySeqNo: number;
|
||||
isEditing: boolean;
|
||||
isLocked: boolean;
|
||||
dragDisabled: boolean;
|
||||
onStartEdit: () => void;
|
||||
onSave: () => void;
|
||||
onCancel: () => void;
|
||||
onDelete: () => void;
|
||||
onChange: (patch: Partial<ElaprPrgrsSttsRow>) => void;
|
||||
onPickAprvr: () => void;
|
||||
onToggleDcarb: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
function SortablePrgrsSttsRow({
|
||||
row,
|
||||
displaySeqNo,
|
||||
isEditing,
|
||||
isLocked,
|
||||
dragDisabled,
|
||||
onStartEdit,
|
||||
onSave,
|
||||
onCancel,
|
||||
onDelete,
|
||||
onChange,
|
||||
onPickAprvr,
|
||||
onToggleDcarb,
|
||||
}: SortablePrgrsSttsRowProps) {
|
||||
const { data: atrzSeCodes } = useCodeList("ATRZSE");
|
||||
const selectableAtrzSeCodes = atrzSeCodes?.filter((code) => code.lwpoCd !== "ATRZ01");
|
||||
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: row.rowKey,
|
||||
disabled: dragDisabled,
|
||||
});
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.6 : 1,
|
||||
};
|
||||
|
||||
const isEditable = isPrgrsSttsRowEditable(row);
|
||||
const isDcarbable = isPrgrsSttsRowDcarbable(row);
|
||||
const rowDisabled = isLocked && !isEditing;
|
||||
const atrzSeLabel = row.atrzSeComNm || findCodeLabel(atrzSeCodes, row.atrzSeComCd);
|
||||
const status = getApproverStatus(row.atrzPrgrsSttsComCd);
|
||||
const isRejectedRow = row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_004";
|
||||
const rjctRsn = row.rjctRsn?.trim();
|
||||
|
||||
const statusBadge = (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center rounded-sm px-3 py-1.5 text-lg font-semibold",
|
||||
status.badgeClass,
|
||||
isRejectedRow && rjctRsn && "cursor-help underline decoration-dotted underline-offset-4",
|
||||
)}
|
||||
>
|
||||
{status.label}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow ref={setNodeRef} style={style} className={isEditing ? "bg-accent/30" : undefined}>
|
||||
<TableCell align="center" className="w-10">
|
||||
{!dragDisabled && (
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
className="cursor-grab !bg-transparent text-muted-foreground disabled:cursor-not-allowed disabled:opacity-40"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
aria-label="순서 변경"
|
||||
>
|
||||
<MoveVertical className="!size-6" />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">{displaySeqNo}</TableCell>
|
||||
<TableCell align="center">
|
||||
{isEditing ? (
|
||||
<Select
|
||||
value={row.atrzSeComCd ?? ""}
|
||||
onValueChange={(v) =>
|
||||
onChange({ atrzSeComCd: v, atrzSeComNm: findCodeLabel(selectableAtrzSeCodes, v) })
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="min-w-[120px]">
|
||||
<SelectValue placeholder="결재구분" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{selectableAtrzSeCodes?.map((code) => (
|
||||
<SelectItem key={code.lwpoCd} value={code.lwpoCd}>
|
||||
{code.lwpoCdNm}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
) : (
|
||||
atrzSeLabel || "-"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span>{row.aprvrNm || row.aprvrUuid || "-"}</span>
|
||||
{isEditing && (
|
||||
<Button type="button" size="sm" onClick={onPickAprvr}>
|
||||
선택
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{!isEditing && (row.ognzNm || row.jbpsSeNm || row.jbgdSeNm) && (
|
||||
<div className="text-base text-gray-500">
|
||||
{row.ognzNm ?? ""}
|
||||
{row.jbpsSeNm ? ` · ${row.jbpsSeNm}` : ""}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{isDcarbable && !rowDisabled && (
|
||||
<Checkbox
|
||||
checked={row.dcarbYn === "Y"}
|
||||
onCheckedChange={(checked) => onToggleDcarb(checked === true)}
|
||||
aria-label="전결자 지정"
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{isRejectedRow && rjctRsn ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>{statusBadge}</TooltipTrigger>
|
||||
<TooltipContent sideOffset={4} className="max-w-md whitespace-pre-wrap">
|
||||
{rjctRsn}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
statusBadge
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<div className="flex justify-center gap-2">
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Button type="button" size="sm" onClick={onSave}>
|
||||
확인
|
||||
</Button>
|
||||
<Button type="button" size="sm" color="white" onClick={onCancel}>
|
||||
취소
|
||||
</Button>
|
||||
</>
|
||||
) : isEditable ? (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="white"
|
||||
disabled={rowDisabled}
|
||||
onClick={onStartEdit}
|
||||
>
|
||||
수정
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="gray"
|
||||
disabled={rowDisabled}
|
||||
onClick={onDelete}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">-</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
export { SortablePrgrsSttsRow };
|
||||
@@ -11,50 +11,41 @@ import type {
|
||||
EsntlAtchFileMngTreeSearchParams,
|
||||
} from "../model/esntl-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.ESNTL_ATCH_FILE;
|
||||
|
||||
async function findTree(
|
||||
params: EsntlAtchFileMngTreeSearchParams,
|
||||
): Promise<EsntlAtchFileMngTreeResponseDto[]> {
|
||||
const response = await client.get<EsntlAtchFileMngTreeResponseDto[]>(
|
||||
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/list/tree`,
|
||||
{ params },
|
||||
);
|
||||
const response = await client.get<EsntlAtchFileMngTreeResponseDto[]>(`${BASE}/list/tree`, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findPage(
|
||||
params: EsntlAtchFileMngSearchParams,
|
||||
): Promise<PageResponse<EsntlAtchFileMng>> {
|
||||
const response = await client.get<PageResponse<EsntlAtchFileMng>>(
|
||||
ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE,
|
||||
{ params },
|
||||
);
|
||||
const response = await client.get<PageResponse<EsntlAtchFileMng>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findById(esntlAtchFileId: string): Promise<EsntlAtchFileMng> {
|
||||
const response = await client.get<EsntlAtchFileMng>(
|
||||
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/${esntlAtchFileId}`,
|
||||
);
|
||||
const response = await client.get<EsntlAtchFileMng>(`${BASE}/${esntlAtchFileId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function create(body: EsntlAtchFileMngSaveRequest): Promise<string> {
|
||||
const response = await client.post<string>(ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE, body);
|
||||
const response = await client.post<string>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(esntlAtchFileId: string, body: EsntlAtchFileMngSaveRequest): Promise<string> {
|
||||
const response = await client.put<string>(
|
||||
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/${esntlAtchFileId}`,
|
||||
body,
|
||||
);
|
||||
const response = await client.put<string>(`${BASE}/${esntlAtchFileId}`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(esntlAtchFileId: string): Promise<string> {
|
||||
const response = await client.delete<string>(
|
||||
`${ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE}/${esntlAtchFileId}`,
|
||||
);
|
||||
const response = await client.delete<string>(`${BASE}/${esntlAtchFileId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
|
||||
import { esntlAtchFileMngService } from "./esntl-service";
|
||||
|
||||
const esntlAtchFileMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.ESNTL_ATCH_FILE] as const;
|
||||
const esntlAtchFileMngBaseKey = [ENDPOINT.AS_SERVICE.ESNTL_ATCH_FILE] as const;
|
||||
|
||||
export function useEsntlAtchFileMngTree(params: EsntlAtchFileMngTreeSearchParams) {
|
||||
return useQuery({
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngDetResponseDto,
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngService,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
} from "../model/fnrs-cnsltn-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.FNRS_CNSLTN_MNG;
|
||||
|
||||
async function findPage(
|
||||
searchDto: FnrsCnsltnMngSearchDto,
|
||||
): Promise<PageResponse<FnrsCnsltnMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<FnrsCnsltnMngResponseDto>>(BASE, {
|
||||
params: searchDto,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findById(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnMngDetResponseDto> {
|
||||
const response = await client.get<FnrsCnsltnMngDetResponseDto>(`${BASE}/${fnrsCnsltnDocNo}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function save(body: FnrsCnsltnMngSaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function saveApply(body: FnrsCnsltnApplySaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/save-apply`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(
|
||||
fnrsCnsltnDocNo: string,
|
||||
body: FnrsCnsltnMngUpdateRequestDto,
|
||||
): Promise<string> {
|
||||
const response = await client.put<string>(`${BASE}/${fnrsCnsltnDocNo}`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function techReview(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<void> {
|
||||
await client.post<void>(`${BASE}/${id}/tech-review`, body);
|
||||
}
|
||||
|
||||
async function submitSupplement(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/${id}/submitsupplement`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function submitReqSupplement(
|
||||
id: string,
|
||||
body: FnrsCnsltnApplySaveRequestDto,
|
||||
): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/${id}/reqsupplement`, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(fnrsCnsltnDocNo: string): Promise<void> {
|
||||
await client.delete<void>(`${BASE}/${fnrsCnsltnDocNo}`);
|
||||
}
|
||||
|
||||
async function changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void> {
|
||||
await client.post<void>(`${BASE}/doc/updatePrgrsStts`, body);
|
||||
}
|
||||
|
||||
export const fnrsCnsltnMngService = {
|
||||
findPage,
|
||||
findById,
|
||||
save,
|
||||
saveApply,
|
||||
update,
|
||||
techReview,
|
||||
submitSupplement,
|
||||
submitReqSupplement,
|
||||
remove,
|
||||
changePrgrsSttsComCd,
|
||||
} satisfies FnrsCnsltnMngService;
|
||||
@@ -0,0 +1,131 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
} from "../model/fnrs-cnsltn-interface";
|
||||
|
||||
import { fnrsCnsltnMngService } from "./fnrs-cnsltn-service";
|
||||
|
||||
export const fnrsCnsltnMngBaseKey = [ENDPOINT.AS_SERVICE.FNRS_CNSLTN_MNG] as const;
|
||||
|
||||
export function useFnrsCnsltnMngPage(params: FnrsCnsltnMngSearchDto) {
|
||||
return useQuery({
|
||||
queryKey: [...fnrsCnsltnMngBaseKey, "findPage", params],
|
||||
queryFn: () => fnrsCnsltnMngService.findPage(params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useFnrsCnsltnMng(fnrsCnsltnDocNo: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...fnrsCnsltnMngBaseKey, "findById", fnrsCnsltnDocNo],
|
||||
queryFn: () => fnrsCnsltnMngService.findById(fnrsCnsltnDocNo!),
|
||||
enabled: Boolean(fnrsCnsltnDocNo),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: FnrsCnsltnMngSaveRequestDto) => fnrsCnsltnMngService.save(body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveApplyFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: FnrsCnsltnApplySaveRequestDto) => fnrsCnsltnMngService.saveApply(body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
fnrsCnsltnDocNo,
|
||||
body,
|
||||
}: {
|
||||
fnrsCnsltnDocNo: string;
|
||||
body: FnrsCnsltnMngUpdateRequestDto;
|
||||
}) => fnrsCnsltnMngService.update(fnrsCnsltnDocNo, body),
|
||||
onSuccess: (_data, { fnrsCnsltnDocNo }) => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...fnrsCnsltnMngBaseKey, "findById", fnrsCnsltnDocNo],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTechReviewFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: FnrsCnsltnApplySaveRequestDto }) =>
|
||||
fnrsCnsltnMngService.techReview(id, body),
|
||||
onSuccess: (_data, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...fnrsCnsltnMngBaseKey, "findById", id],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubmitSupplementFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: FnrsCnsltnApplySaveRequestDto }) =>
|
||||
fnrsCnsltnMngService.submitSupplement(id, body),
|
||||
onSuccess: (_data, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...fnrsCnsltnMngBaseKey, "findById", id],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubmitReqSupplementFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: FnrsCnsltnApplySaveRequestDto }) =>
|
||||
fnrsCnsltnMngService.submitReqSupplement(id, body),
|
||||
onSuccess: (_data, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...fnrsCnsltnMngBaseKey, "findById", id],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteFnrsCnsltnMng() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (fnrsCnsltnDocNo: string) => fnrsCnsltnMngService.remove(fnrsCnsltnDocNo),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useChangePrgrsSttsComCd() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (body: DocPrgrsUpdateRequestDto) => fnrsCnsltnMngService.changePrgrsSttsComCd(body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsCnsltnMngBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
export { fnrsCnsltnMngService } from "./api/fnrs-cnsltn-service";
|
||||
export { fnrsCnsltnMngBaseKey } from "./api/queries";
|
||||
export {
|
||||
useChangePrgrsSttsComCd,
|
||||
useDeleteFnrsCnsltnMng,
|
||||
useFnrsCnsltnMng,
|
||||
useFnrsCnsltnMngPage,
|
||||
useSaveApplyFnrsCnsltnMng,
|
||||
useSaveFnrsCnsltnMng,
|
||||
useSubmitReqSupplementFnrsCnsltnMng,
|
||||
useSubmitSupplementFnrsCnsltnMng,
|
||||
useTechReviewFnrsCnsltnMng,
|
||||
useUpdateFnrsCnsltnMng,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstItem,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
BzScaleItem,
|
||||
CklstResultItem,
|
||||
CklstRvwRsltTreeDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileItem,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
FnrsCnsltnApplySaveRequestDto,
|
||||
FnrsCnsltnMngDetResponseDto,
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnMngSaveRequestDto,
|
||||
FnrsCnsltnMngSearchDto,
|
||||
FnrsCnsltnMngService,
|
||||
FnrsCnsltnMngUpdateRequestDto,
|
||||
FnrsCnsltnRvwOpnnResponseDto,
|
||||
FnrsCnsltnRvwOpnnSaveRequestDto,
|
||||
FnrsCnsltnSplmntDmndItem,
|
||||
FnrsCnsltnSplmntDmndResponseDto,
|
||||
FnrsSharItem,
|
||||
} from "./model/fnrs-cnsltn-interface";
|
||||
@@ -0,0 +1,177 @@
|
||||
import type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstSaveBase,
|
||||
BzScaleSaveBase,
|
||||
CklstResultSaveBase,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
EsntlAtchFileSaveBase,
|
||||
PageableParams,
|
||||
PageResponse,
|
||||
PbadmsApplySaveHeader,
|
||||
PbadmsMngDetResponseBase,
|
||||
RvwOpnnResponseExtension,
|
||||
RvwOpnnSaveBase,
|
||||
SplmntDmndResponseExtension,
|
||||
SplmntDmndSaveBase,
|
||||
} from "@/shared/model";
|
||||
import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se";
|
||||
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
CklstRvwRsltTreeDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
} from "@/shared/model";
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngSaveRequestDto */
|
||||
export interface FnrsCnsltnMngSaveRequestDto {
|
||||
fnrsCnsltnDocNo?: string;
|
||||
admdstCd?: string;
|
||||
envoSeComCd?: string;
|
||||
taskSeComCd?: string;
|
||||
cfmtnBgtBizNo?: string;
|
||||
bgtAplyYr?: string;
|
||||
bizNm?: string;
|
||||
bizBgngYr?: string;
|
||||
bizEndYr?: string;
|
||||
bizCn?: string;
|
||||
aplcntNm?: string;
|
||||
aplcntMblTelno?: string;
|
||||
aplcntOgdpNm?: string;
|
||||
aplcntEmlAddr?: string;
|
||||
dsgnrNm?: string;
|
||||
dsgnrTelno?: string;
|
||||
dsgnrOgdpNm?: string;
|
||||
dsgnrEmlAddr?: string;
|
||||
atchFileGroupId?: string;
|
||||
sharAtchFileGroupId?: string;
|
||||
prgrsSttsComCd?: string;
|
||||
rtlztnFcltSeComCd?: string;
|
||||
rtlztnUsgSeComCd?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngUpdateRequestDto */
|
||||
export type FnrsCnsltnMngUpdateRequestDto = Omit<FnrsCnsltnMngSaveRequestDto, "fnrsCnsltnDocNo">;
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngSearchDto + Pageable */
|
||||
export interface FnrsCnsltnMngSearchDto extends PageableParams {
|
||||
fnrsCnsltnDocNo?: string;
|
||||
admdstCd?: string;
|
||||
envoSeComCd?: string;
|
||||
taskSeComCd?: string;
|
||||
cfmtnBgtBizNo?: string;
|
||||
bgtAplyYr?: string;
|
||||
bizNm?: string;
|
||||
bizBgngYr?: string;
|
||||
bizEndYr?: string;
|
||||
bizCn?: string;
|
||||
aplcntNm?: string;
|
||||
aplcntMblTelno?: string;
|
||||
aplcntOgdpNm?: string;
|
||||
aplcntEmlAddr?: string;
|
||||
dsgnrNm?: string;
|
||||
dsgnrTelno?: string;
|
||||
dsgnrOgdpNm?: string;
|
||||
dsgnrEmlAddr?: string;
|
||||
atchFileGroupId?: string;
|
||||
sharAtchFileGroupId?: string;
|
||||
prgrsSttsComCd?: string;
|
||||
}
|
||||
|
||||
interface FnrsCnsltnMngResponseExtension {
|
||||
fnrsCnsltnDocNo: string;
|
||||
envoSeCdNm?: string;
|
||||
taskSeCdNm?: string;
|
||||
prgrsSttsCdNm?: string;
|
||||
regDt?: string;
|
||||
rgtr?: string;
|
||||
rgtrNm?: string;
|
||||
mdfcnDt?: string;
|
||||
sido?: string;
|
||||
gugun?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngResponseDto */
|
||||
export type FnrsCnsltnMngResponseDto = Omit<FnrsCnsltnMngSaveRequestDto, "fnrsCnsltnDocNo"> &
|
||||
FnrsCnsltnMngResponseExtension;
|
||||
|
||||
export type BzScaleItem = BzScaleSaveBase;
|
||||
|
||||
export type BizCstItem = BizCstSaveBase;
|
||||
|
||||
export interface CklstResultItem extends CklstResultSaveBase {
|
||||
selected?: string;
|
||||
}
|
||||
|
||||
export type EsntlAtchFileItem = EsntlAtchFileSaveBase;
|
||||
|
||||
export type FnrsCnsltnSplmntDmndItem = SplmntDmndSaveBase;
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */
|
||||
export interface FnrsSharItem {
|
||||
fnrsSharSeCd?: string;
|
||||
exstnAmt?: number;
|
||||
chgRqtAmt?: number;
|
||||
ajmtAmt?: number;
|
||||
rmrkCn?: string;
|
||||
envoRmrkCn?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */
|
||||
export interface FnrsCnsltnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase {
|
||||
fnrsCnsltnDocNo: string;
|
||||
rvwOpnnSn: number;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnResponseDto */
|
||||
export type FnrsCnsltnRvwOpnnResponseDto = Partial<
|
||||
Pick<FnrsCnsltnRvwOpnnSaveRequestDto, "fnrsCnsltnDocNo" | "rvwOpnnSn">
|
||||
> &
|
||||
Omit<FnrsCnsltnRvwOpnnSaveRequestDto, "fnrsCnsltnDocNo" | "rvwOpnnSn"> &
|
||||
RvwOpnnResponseExtension;
|
||||
|
||||
/** 백엔드 FnrsCnsltnSplmntDmndResponseDto */
|
||||
export type FnrsCnsltnSplmntDmndResponseDto = FnrsCnsltnSplmntDmndItem &
|
||||
SplmntDmndResponseExtension & {
|
||||
fnrsCnsltnDocNo?: string;
|
||||
};
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngDetResponseDto */
|
||||
export type FnrsCnsltnMngDetResponseDto = PbadmsMngDetResponseBase<
|
||||
FnrsCnsltnMngResponseDto,
|
||||
FnrsCnsltnRvwOpnnResponseDto,
|
||||
FnrsCnsltnSplmntDmndResponseDto
|
||||
> & {
|
||||
fnrsSharList?: FnrsSharBizCstTreeDto[];
|
||||
sharAtchFileList?: AtflMngClientResponseDto[];
|
||||
};
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
|
||||
export interface FnrsCnsltnApplySaveRequestDto extends PbadmsApplySaveHeader {
|
||||
fnrsCnsltnMng: FnrsCnsltnMngSaveRequestDto;
|
||||
bzScaleList?: BzScaleItem[];
|
||||
bizCstList?: BizCstItem[];
|
||||
cklstResultList?: CklstResultItem[];
|
||||
cklstResultMap?: Record<string, CklstResultItem[]>;
|
||||
esntlAtchFileList?: EsntlAtchFileItem[];
|
||||
rvwOpnnList?: FnrsCnsltnRvwOpnnSaveRequestDto[];
|
||||
splmntDmndList?: FnrsCnsltnSplmntDmndItem[];
|
||||
fnrsSharList?: FnrsSharItem[];
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface FnrsCnsltnMngService {
|
||||
findPage(searchDto: FnrsCnsltnMngSearchDto): Promise<PageResponse<FnrsCnsltnMngResponseDto>>;
|
||||
findById(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnMngDetResponseDto>;
|
||||
save(body: FnrsCnsltnMngSaveRequestDto): Promise<string>;
|
||||
saveApply(body: FnrsCnsltnApplySaveRequestDto): Promise<string>;
|
||||
update(fnrsCnsltnDocNo: string, body: FnrsCnsltnMngUpdateRequestDto): Promise<string>;
|
||||
techReview(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<void>;
|
||||
submitSupplement(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<string>;
|
||||
submitReqSupplement(id: string, body: FnrsCnsltnApplySaveRequestDto): Promise<string>;
|
||||
remove(fnrsCnsltnDocNo: string): Promise<void>;
|
||||
changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import {
|
||||
FNRS_SHAR_SE_SAVE_MODE,
|
||||
type FnrsSharBizCstTreeDto,
|
||||
type FnrsSharSeMngResponseDto,
|
||||
type FnrsSharSeMngSaveRequestDto,
|
||||
type FnrsSharSeMngSearchDto,
|
||||
type FnrsSharSeMngService,
|
||||
type FnrsSharSeMngTreeDto,
|
||||
} from "../model/fnrs-shar-se-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.FNRS_SHAR_SE;
|
||||
|
||||
async function findAll(
|
||||
params: FnrsSharSeMngSearchDto,
|
||||
): Promise<PageResponse<FnrsSharSeMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<FnrsSharSeMngResponseDto>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findTree(): Promise<FnrsSharSeMngTreeDto[]> {
|
||||
const response = await client.get<FnrsSharSeMngTreeDto[]>(`${BASE}/list/tree`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findById(fnrsSharSeCd: string): Promise<FnrsSharSeMngResponseDto> {
|
||||
const response = await client.get<FnrsSharSeMngResponseDto>(`${BASE}/${fnrsSharSeCd}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function saveMulti(
|
||||
body: FnrsSharSeMngSaveRequestDto[],
|
||||
): Promise<FnrsSharSeMngResponseDto[]> {
|
||||
const response = await client.post<FnrsSharSeMngResponseDto[]>(BASE, body);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(fnrsSharSeCd: string, body: FnrsSharSeMngSaveRequestDto): Promise<string> {
|
||||
const response = await client.put<string>(`${BASE}/${fnrsSharSeCd}`, {
|
||||
...body,
|
||||
fnrsSharSeCd,
|
||||
mode: FNRS_SHAR_SE_SAVE_MODE.UPDATE,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function deleteFnrsSharSeCd(fnrsSharSeCd: string): Promise<string> {
|
||||
const response = await client.delete<string>(`${BASE}/${fnrsSharSeCd}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findTreeFnrsShar(fnrsCnsltnDocNo: string): Promise<FnrsSharBizCstTreeDto[]> {
|
||||
const response = await client.get<FnrsSharBizCstTreeDto[]>(
|
||||
`${BASE}/list/tree/fnrs-shar/${fnrsCnsltnDocNo}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const fnrsSharSeMngService = {
|
||||
findAll,
|
||||
findTree,
|
||||
findById,
|
||||
saveMulti,
|
||||
update,
|
||||
delete: deleteFnrsSharSeCd,
|
||||
findTreeFnrsShar,
|
||||
} satisfies FnrsSharSeMngService;
|
||||
@@ -0,0 +1,85 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
FnrsSharSeMngSaveRequestDto,
|
||||
FnrsSharSeMngSearchDto,
|
||||
} from "../model/fnrs-shar-se-interface";
|
||||
|
||||
import { fnrsSharSeMngService } from "./fnrs-shar-se-service";
|
||||
|
||||
const fnrsSharSeMngBaseKey = [ENDPOINT.AS_SERVICE.FNRS_SHAR_SE] as const;
|
||||
|
||||
export function useFnrsSharSeMngPage(params: FnrsSharSeMngSearchDto) {
|
||||
return useQuery({
|
||||
queryKey: [...fnrsSharSeMngBaseKey, "findAll", params],
|
||||
queryFn: () => fnrsSharSeMngService.findAll(params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useFnrsSharSeMngTree() {
|
||||
return useQuery({
|
||||
queryKey: [...fnrsSharSeMngBaseKey, "findTree"],
|
||||
queryFn: () => fnrsSharSeMngService.findTree(),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
}
|
||||
|
||||
export function useFnrsSharSeMng(fnrsSharSeCd: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...fnrsSharSeMngBaseKey, "findById", fnrsSharSeCd],
|
||||
queryFn: () => fnrsSharSeMngService.findById(fnrsSharSeCd!),
|
||||
enabled: Boolean(fnrsSharSeCd),
|
||||
});
|
||||
}
|
||||
|
||||
export function useSaveMultiFnrsSharSeMng() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (body: FnrsSharSeMngSaveRequestDto[]) => fnrsSharSeMngService.saveMulti(body),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsSharSeMngBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateFnrsSharSeMng() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
fnrsSharSeCd,
|
||||
body,
|
||||
}: {
|
||||
fnrsSharSeCd: string;
|
||||
body: FnrsSharSeMngSaveRequestDto;
|
||||
}) => fnrsSharSeMngService.update(fnrsSharSeCd, body),
|
||||
onSuccess: (_data, { fnrsSharSeCd }) => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsSharSeMngBaseKey });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [...fnrsSharSeMngBaseKey, "findById", fnrsSharSeCd],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteFnrsSharSeMng() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (fnrsSharSeCd: string) => fnrsSharSeMngService.delete(fnrsSharSeCd),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: fnrsSharSeMngBaseKey });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useFnrsSharBizCstTree(fnrsCnsltnDocNo: string | null | undefined) {
|
||||
return useQuery({
|
||||
queryKey: [...fnrsSharSeMngBaseKey, "findTreeFnrsShar", fnrsCnsltnDocNo],
|
||||
queryFn: () => fnrsSharSeMngService.findTreeFnrsShar(fnrsCnsltnDocNo!),
|
||||
enabled: Boolean(fnrsCnsltnDocNo),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export { fnrsSharSeMngService } from "./api/fnrs-shar-se-service";
|
||||
export {
|
||||
useDeleteFnrsSharSeMng,
|
||||
useFnrsSharBizCstTree,
|
||||
useFnrsSharSeMng,
|
||||
useFnrsSharSeMngPage,
|
||||
useFnrsSharSeMngTree,
|
||||
useSaveMultiFnrsSharSeMng,
|
||||
useUpdateFnrsSharSeMng,
|
||||
} from "./api/queries";
|
||||
export type {
|
||||
FnrsSharBizCstTreeDto,
|
||||
FnrsSharSeMngResponseDto,
|
||||
FnrsSharSeMngSaveRequestDto,
|
||||
FnrsSharSeMngSearchDto,
|
||||
FnrsSharSeMngService,
|
||||
FnrsSharSeMngTreeDto,
|
||||
FnrsSharSeSaveMode,
|
||||
} from "./model/fnrs-shar-se-interface";
|
||||
export { FNRS_SHAR_SE_SAVE_MODE } from "./model/fnrs-shar-se-interface";
|
||||
@@ -0,0 +1,83 @@
|
||||
import type { PageableParams, PageResponse } from "@/shared/model";
|
||||
|
||||
export const FNRS_SHAR_SE_SAVE_MODE = {
|
||||
CREATE: "C",
|
||||
UPDATE: "U",
|
||||
DELETE: "D",
|
||||
} as const;
|
||||
|
||||
export type FnrsSharSeSaveMode = (typeof FNRS_SHAR_SE_SAVE_MODE)[keyof typeof FNRS_SHAR_SE_SAVE_MODE];
|
||||
|
||||
/** 백엔드 FnrsSharSeMngResponseDto */
|
||||
export interface FnrsSharSeMngResponseDto {
|
||||
fnrsSharSeCd: string;
|
||||
fnrsSharSeCdNm: string;
|
||||
upFnrsSharSeCd?: string;
|
||||
cdLvNo: number;
|
||||
useYn: string;
|
||||
expln?: string;
|
||||
sortSeq?: number;
|
||||
rgtr?: string;
|
||||
rgtrNm?: string;
|
||||
mdfr?: string;
|
||||
regDt?: string;
|
||||
mdfcnDt?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsSharSeMngSearchDto + Pageable */
|
||||
export interface FnrsSharSeMngSearchDto extends PageableParams {
|
||||
fnrsSharSeCd?: string;
|
||||
fnrsSharSeCdNm?: string;
|
||||
useYn?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsSharSeMngSaveRequestDto */
|
||||
export interface FnrsSharSeMngSaveRequestDto {
|
||||
fnrsSharSeCd: string;
|
||||
mode: FnrsSharSeSaveMode;
|
||||
fnrsSharSeCdNm: string;
|
||||
upFnrsSharSeCd?: string;
|
||||
cdLvNo: number;
|
||||
useYn?: string;
|
||||
expln?: string;
|
||||
sortSeq?: number;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsSharSeMngTreeDto */
|
||||
export interface FnrsSharSeMngTreeDto {
|
||||
indFnrsSharSeCdNm: string;
|
||||
fnrsSharSeCd: string;
|
||||
fnrsSharSeCdNm: string;
|
||||
upFnrsSharSeCd?: string;
|
||||
useYn: string;
|
||||
treeLevel: number;
|
||||
treePath: string;
|
||||
sortPath: string;
|
||||
}
|
||||
|
||||
/** 백엔드 FnrsSharBizCstTreeDto */
|
||||
export interface FnrsSharBizCstTreeDto {
|
||||
indFnrsSharSeCdNm: string;
|
||||
fnrsSharSeCd: string;
|
||||
fnrsSharSeCdNm: string;
|
||||
useYn: string;
|
||||
treeLevel: number;
|
||||
treePath: string;
|
||||
sortPath: string;
|
||||
exstnAmt?: number;
|
||||
chgRqtAmt?: number;
|
||||
ajmtAmt?: number;
|
||||
rmrkCn?: string;
|
||||
envoRmrkCn?: string;
|
||||
}
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface FnrsSharSeMngService {
|
||||
findAll(params: FnrsSharSeMngSearchDto): Promise<PageResponse<FnrsSharSeMngResponseDto>>;
|
||||
findTree(): Promise<FnrsSharSeMngTreeDto[]>;
|
||||
findById(fnrsSharSeCd: string): Promise<FnrsSharSeMngResponseDto>;
|
||||
saveMulti(body: FnrsSharSeMngSaveRequestDto[]): Promise<FnrsSharSeMngResponseDto[]>;
|
||||
update(fnrsSharSeCd: string, body: FnrsSharSeMngSaveRequestDto): Promise<string>;
|
||||
delete(fnrsSharSeCd: string): Promise<string>;
|
||||
findTreeFnrsShar(fnrsCnsltnDocNo: string): Promise<FnrsSharBizCstTreeDto[]>;
|
||||
}
|
||||
@@ -1,41 +1,109 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
AdstZoneResponseDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnMng,
|
||||
InstlSncnMngDetResponse,
|
||||
InstlSncnMngResponseDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
InstlSncnMngSearchParams,
|
||||
InstlSncnMngService,
|
||||
SpringPage,
|
||||
InstlSncnMngDetResponse,
|
||||
InstlSncnMngUpdateRequestDto,
|
||||
} from "../model/instl-sncn-interface";
|
||||
|
||||
export class InstlSncnApiService implements InstlSncnMngService {
|
||||
public async getPage(params: InstlSncnMngSearchParams): Promise<SpringPage<InstlSncnMng>> {
|
||||
const response = await client.get<SpringPage<InstlSncnMng>>(ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
async function getSidoCombo(): Promise<AdstZoneResponseDto[]> {
|
||||
const response = await client.get<AdstZoneResponseDto[]>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`,
|
||||
);
|
||||
|
||||
public async getById(id: string): Promise<InstlSncnMngDetResponse> {
|
||||
const response = await client.get<InstlSncnMngDetResponse>(`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async saveApply(payload: InstlSncnApplySaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(
|
||||
`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/save-apply`,
|
||||
payload,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
public async techReview(payload: InstlSncnApplySaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(
|
||||
`${ENDPOINT.AS_INSTL_SERVICE.INSTL_SNCN}/tech-review`,
|
||||
payload,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const InstlSncnApi = new InstlSncnApiService();
|
||||
async function getSigunguCombo(sidoCd: string): Promise<AdstZoneResponseDto[]> {
|
||||
const response = await client.get<AdstZoneResponseDto[]>(
|
||||
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`,
|
||||
{ params: { sidoCd } },
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
const BASE = ENDPOINT.AS_SERVICE.INSTL_SNCN;
|
||||
|
||||
async function getPage(
|
||||
params: InstlSncnMngSearchParams,
|
||||
): Promise<PageResponse<InstlSncnMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<InstlSncnMngResponseDto>>(BASE, {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function getById(id: string): Promise<InstlSncnMngDetResponse> {
|
||||
const response = await client.get<InstlSncnMngDetResponse>(`${BASE}/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function save(payload: InstlSncnMngSaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(BASE, payload);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function saveApply(payload: InstlSncnApplySaveRequestDto): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/save-apply`, payload);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(id: string, payload: InstlSncnMngUpdateRequestDto): Promise<void> {
|
||||
await client.put(`${BASE}/${id}`, payload);
|
||||
}
|
||||
|
||||
async function techReview(id: string, payload: InstlSncnApplySaveRequestDto): Promise<void> {
|
||||
await client.post(`${BASE}/${id}/tech-review`, payload);
|
||||
}
|
||||
|
||||
async function submitSupplement(
|
||||
id: string,
|
||||
payload: InstlSncnApplySaveRequestDto,
|
||||
): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/${id}/submitsupplement`, payload);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function submitReqSupplement(
|
||||
id: string,
|
||||
payload: InstlSncnApplySaveRequestDto,
|
||||
): Promise<string> {
|
||||
const response = await client.post<string>(`${BASE}/${id}/reqsupplement`, payload);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function remove(id: string): Promise<void> {
|
||||
await client.delete(`${BASE}/${id}`);
|
||||
}
|
||||
|
||||
async function changePrgrsSttsComCd(payload: DocPrgrsUpdateRequestDto): Promise<void> {
|
||||
await client.post(`${BASE}/doc/updatePrgrsStts`, payload);
|
||||
}
|
||||
|
||||
export const instlSncnService = {
|
||||
getPage,
|
||||
getById,
|
||||
save,
|
||||
saveApply,
|
||||
update,
|
||||
techReview,
|
||||
submitSupplement,
|
||||
submitReqSupplement,
|
||||
remove,
|
||||
changePrgrsSttsComCd,
|
||||
} satisfies InstlSncnMngService;
|
||||
|
||||
// TODO: st-anls-mst service 로 분리
|
||||
export const instlSncnRegionService = {
|
||||
getSidoCombo,
|
||||
getSigunguCombo,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
DocPrgrsUpdateRequestDto,
|
||||
InstlSncnApplySaveRequestDto,
|
||||
InstlSncnMngSaveRequestDto,
|
||||
InstlSncnMngSearchParams,
|
||||
InstlSncnMngUpdateRequestDto,
|
||||
} from "../model/instl-sncn-interface";
|
||||
|
||||
import { instlSncnRegionService, instlSncnService } from "./instl-sncn-service";
|
||||
|
||||
export const instlSncnKeys = {
|
||||
all: ["instl-sncn"] as const,
|
||||
list: () => [...instlSncnKeys.all, "list"] as const,
|
||||
detail: (id: string) => [...instlSncnKeys.all, "detail", id] as const,
|
||||
sidoCombo: () => [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sido"] as const,
|
||||
sigunguCombo: (sidoCd: string) =>
|
||||
[ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sigungu", sidoCd] as const,
|
||||
};
|
||||
|
||||
export function useInstlSncnList(params: InstlSncnMngSearchParams) {
|
||||
return useQuery({
|
||||
queryKey: [...instlSncnKeys.list(), params],
|
||||
queryFn: () => instlSncnService.getPage(params),
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnDetail(id: string) {
|
||||
return useQuery({
|
||||
queryKey: instlSncnKeys.detail(id),
|
||||
queryFn: () => instlSncnService.getById(id),
|
||||
enabled: Boolean(id),
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnSidoCombo() {
|
||||
return useQuery({
|
||||
queryKey: instlSncnKeys.sidoCombo(),
|
||||
queryFn: () => instlSncnRegionService.getSidoCombo(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnSigunguCombo(sidoCd: string) {
|
||||
return useQuery({
|
||||
queryKey: instlSncnKeys.sigunguCombo(sidoCd),
|
||||
queryFn: () => instlSncnRegionService.getSigunguCombo(sidoCd),
|
||||
enabled: Boolean(sidoCd),
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnSave() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: InstlSncnMngSaveRequestDto) => instlSncnService.save(payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnSaveApply() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: InstlSncnApplySaveRequestDto) => instlSncnService.saveApply(payload),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnUpdate() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, payload }: { id: string; payload: InstlSncnMngUpdateRequestDto }) =>
|
||||
instlSncnService.update(id, payload),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.detail(variables.id) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnTechReview() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, payload }: { id: string; payload: InstlSncnApplySaveRequestDto }) =>
|
||||
instlSncnService.techReview(id, payload),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.detail(variables.id) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnSubmitSupplement() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, payload }: { id: string; payload: InstlSncnApplySaveRequestDto }) =>
|
||||
instlSncnService.submitSupplement(id, payload),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.detail(variables.id) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnSubmitReqSupplement() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, payload }: { id: string; payload: InstlSncnApplySaveRequestDto }) =>
|
||||
instlSncnService.submitReqSupplement(id, payload),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.detail(variables.id) });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnRemove() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => instlSncnService.remove(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useInstlSncnChangePrgrsStts() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: DocPrgrsUpdateRequestDto) =>
|
||||
instlSncnService.changePrgrsSttsComCd(payload),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: instlSncnKeys.detail(variables.docNo),
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,12 +1,36 @@
|
||||
export { InstlSncnApi } from "./api/instl-sncn-service";
|
||||
export { instlSncnRegionService, instlSncnService } from "./api/instl-sncn-service";
|
||||
export {
|
||||
instlSncnKeys,
|
||||
useInstlSncnDetail,
|
||||
useInstlSncnList,
|
||||
useInstlSncnSidoCombo,
|
||||
useInstlSncnSigunguCombo,
|
||||
useInstlSncnSubmitReqSupplement,
|
||||
useInstlSncnSubmitSupplement,
|
||||
useInstlSncnTechReview,
|
||||
} from "./api/queries";
|
||||
export {
|
||||
type AdstZoneResponseDto,
|
||||
type AtflMngClientResponseDto,
|
||||
type BizCstTreeDto,
|
||||
type BizSclTreeDto,
|
||||
type CklstRvwRsltTreeDto,
|
||||
type DocPrgrsUpdateRequestDto,
|
||||
type ElaprDocPrgrsClientResponseDto,
|
||||
type EsntlAtchFileMngTreeResponseDto,
|
||||
INSTL_SNCN_REGISTER_SELECTION_KEY,
|
||||
type InstlSncnApplySaveRequestDto,
|
||||
type InstlSncnBizCstSaveRequestDto,
|
||||
type InstlSncnBzScaleSaveDto,
|
||||
type InstlSncnCklstResultSaveDto,
|
||||
type InstlSncnMng,
|
||||
type InstlSncnEsntlAtchFileSaveDto,
|
||||
type InstlSncnMngDetResponse,
|
||||
type InstlSncnMngResponseDto,
|
||||
type InstlSncnMngSaveRequestDto,
|
||||
type InstlSncnMngSearchParams,
|
||||
type SpringPage,
|
||||
type ElaprDocPrgrsClientResponseDto,
|
||||
type InstlSncnRvwOpnnResponseDto,
|
||||
type InstlSncnRvwOpnnSaveRequestDto,
|
||||
type InstlSncnSplmntDmndResponseDto,
|
||||
type InstlSncnSplmntDmndSaveDto,
|
||||
type ReviewStepCode,
|
||||
} from "./model/instl-sncn-interface";
|
||||
|
||||
@@ -1,78 +1,150 @@
|
||||
export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection";
|
||||
import type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstSaveBase,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
BzScaleSaveBase,
|
||||
CklstResultSaveBase,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
EsntlAtchFileSaveBase,
|
||||
PageableParams,
|
||||
PageResponse,
|
||||
PbadmsApplySaveHeader,
|
||||
PbadmsMngDetResponseBase,
|
||||
RvwOpnnResponseExtension,
|
||||
RvwOpnnSaveBase,
|
||||
SplmntDmndResponseExtension,
|
||||
SplmntDmndSaveBase,
|
||||
} from "@/shared/model";
|
||||
|
||||
export interface SpringPage<T> {
|
||||
content: T[];
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
number: number;
|
||||
size: number;
|
||||
first: boolean;
|
||||
last: boolean;
|
||||
numberOfElements: number;
|
||||
export type {
|
||||
AtflMngClientResponseDto,
|
||||
BizCstTreeDto,
|
||||
BizSclTreeDto,
|
||||
CklstRvwRsltTreeDto,
|
||||
DocPrgrsUpdateRequestDto,
|
||||
ElaprDocPrgrsClientResponseDto,
|
||||
EsntlAtchFileMngTreeResponseDto,
|
||||
} from "@/shared/model";
|
||||
|
||||
/** 행정구역(시도·시군구) 콤보 응답 DTO */
|
||||
export interface AdstZoneResponseDto {
|
||||
/** 행정구역 코드 */
|
||||
adstCd: string;
|
||||
/** 행정구역 명 */
|
||||
adstNm: string;
|
||||
}
|
||||
|
||||
export interface InstlSncnMngSearchParams {
|
||||
export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection";
|
||||
|
||||
export interface InstlSncnMngSearchParams extends PageableParams {
|
||||
bizNm?: string;
|
||||
taskSeComCd?: string;
|
||||
sidoComCd?: string;
|
||||
sggComCd?: string;
|
||||
page?: number;
|
||||
size?: number;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
export interface InstlSncnMng {
|
||||
gugun: string;
|
||||
sido: string;
|
||||
instlSncnDocNo: string;
|
||||
admdstCd: string;
|
||||
sidoNm?: string;
|
||||
sggNm?: string;
|
||||
chrgrNm?: string;
|
||||
instNm?: string;
|
||||
envoSeComCd: string;
|
||||
/** 설치인가관리 저장 요청 DTO */
|
||||
export interface InstlSncnMngSaveRequestDto {
|
||||
/** 설치인가문서번호 */
|
||||
instlSncnDocNo?: string;
|
||||
/** 행정구역코드 */
|
||||
admdstCd?: string;
|
||||
/** 환경청구분공통코드 */
|
||||
envoSeComCd?: string;
|
||||
/** 인가구분공통코드 */
|
||||
sncnSeComCd: string;
|
||||
envoSeCdNm: string;
|
||||
/** 업무구분공통코드 */
|
||||
taskSeComCd: string;
|
||||
taskSeCdNm: string;
|
||||
bgtBizCd: string;
|
||||
bgtAplyYr: string;
|
||||
/** 예산사업코드 */
|
||||
bgtBizCd?: string;
|
||||
/** 예산신청연도 */
|
||||
bgtAplyYr?: string;
|
||||
/** 사업명 */
|
||||
bizNm: string;
|
||||
/** 사업시작연도 */
|
||||
bizBgngYr: string;
|
||||
/** 사업종료연도 */
|
||||
bizEndYr: string;
|
||||
/** 사업내용 */
|
||||
bizCn: string;
|
||||
aplcntNm: string;
|
||||
aplcntMblTelno: string;
|
||||
aplcntOgdpNm: string;
|
||||
aplcntEmlAddr: string;
|
||||
/** 신청자명 */
|
||||
aplcntNm?: string;
|
||||
/** 신청자휴대전화번호 */
|
||||
aplcntMblTelno?: string;
|
||||
/** 신청자소속명 */
|
||||
aplcntOgdpNm?: string;
|
||||
/** 신청자이메일주소 */
|
||||
aplcntEmlAddr?: string;
|
||||
/** 설계자명 */
|
||||
dsgnrNm: string;
|
||||
/** 설계자전화번호 */
|
||||
dsgnrTelno: string;
|
||||
/** 설계자소속명 */
|
||||
dsgnrOgdpNm: string;
|
||||
/** 설계자이메일주소 */
|
||||
dsgnrEmlAddr: string;
|
||||
atchFileGroupId: string;
|
||||
prgrsSttsComCd: string;
|
||||
/** 첨부파일그룹아이디 */
|
||||
atchFileGroupId?: string;
|
||||
/** 진행상태공통코드 */
|
||||
prgrsSttsComCd?: string;
|
||||
/** 재이용시설구분공통코드 */
|
||||
rtlztnFcltSeComCd?: string;
|
||||
/** 재이용용도구분공통코드 */
|
||||
rtlztnUsgSeComCd?: string;
|
||||
}
|
||||
|
||||
/** 설치인가관리 수정 요청 DTO */
|
||||
export type InstlSncnMngUpdateRequestDto = InstlSncnMngSaveRequestDto;
|
||||
|
||||
/** 설치인가관리 응답 DTO */
|
||||
export interface InstlSncnMngResponseDto extends InstlSncnMngSaveRequestDto {
|
||||
/** 설치인가문서번호 (필수로 재정의) */
|
||||
instlSncnDocNo: string;
|
||||
/** 환경청구분공통코드명 */
|
||||
envoSeCdNm: string;
|
||||
/** 인가구분공통코드명 */
|
||||
sncnSeCdNm: string;
|
||||
/** 업무구분공통코드명 */
|
||||
taskSeCdNm: string;
|
||||
/** 진행상태공통코드명 */
|
||||
prgrsSttsCdNm: string;
|
||||
/** 재이용시설구분공통코드명 */
|
||||
rtlztnFcltSeComNm: string;
|
||||
/** 재이용용도구분공통코드명 */
|
||||
rtlztnUsgSeComNm: string;
|
||||
/** 등록일시 */
|
||||
regDt: string;
|
||||
mdfcnDt: string;
|
||||
/** 등록자 UUID */
|
||||
rgtr: string;
|
||||
/** 등록자명 */
|
||||
rgtrNm: string;
|
||||
/** 수정 일시 */
|
||||
mdfcnDt: string;
|
||||
/** 시도 */
|
||||
sido: string;
|
||||
/** 구군 */
|
||||
gugun: string;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 사업규모 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnBzScaleSaveDto {
|
||||
export interface InstlSncnBzScaleSaveDto extends BzScaleSaveBase {
|
||||
/** 공종구분코드 */
|
||||
cnstpSeCd: string;
|
||||
/** 기본계획(A) */
|
||||
/** 기본계획수량 */
|
||||
bscPl: number;
|
||||
/** 예산반영(B) */
|
||||
/** 예산반영수량 */
|
||||
budgetReflct: number;
|
||||
/** 금회설계시(C) */
|
||||
/** 금회설계수량 */
|
||||
thtiDsgn: number;
|
||||
/** 증감(C-B) */
|
||||
diff: number;
|
||||
/** 증감(C-B) — 클라이언트 계산용, API 미전송 */
|
||||
diff?: number;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 총사업비 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnBizCstSaveRequestDto {
|
||||
export interface InstlSncnBizCstSaveRequestDto extends BizCstSaveBase {
|
||||
/** 회계구분코드 */
|
||||
acntgSeCd: string;
|
||||
/** 기본계획금액 */
|
||||
@@ -84,66 +156,101 @@ export interface InstlSncnBizCstSaveRequestDto {
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 체크리스트 결과 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnCklstResultSaveDto {
|
||||
export interface InstlSncnCklstResultSaveDto extends CklstResultSaveBase {
|
||||
/** 점검표일련번호 */
|
||||
cklstSn: number;
|
||||
/** 점검표항목일련번호 */
|
||||
cklstArtclSn: number;
|
||||
/** 점검표검토일련번호 */
|
||||
cklstRvwSn: number;
|
||||
rvwStpComCd: string;
|
||||
/** 선택값(Y/N/null) */
|
||||
selected: "Y" | "N" | null;
|
||||
/** 담당자검토내용(기술검토의견) */
|
||||
picRvwCn?: string | null;
|
||||
}
|
||||
|
||||
/** 설치인가 신청 - 필수첨부파일 저장 DTO(배열 원소) */
|
||||
export interface InstlSncnEsntlAtchFileSaveDto {
|
||||
export interface InstlSncnEsntlAtchFileSaveDto extends EsntlAtchFileSaveBase {
|
||||
/** 필수첨부파일아이디 */
|
||||
esntlAtchFileId: string;
|
||||
/** 첨부파일그룹아이디 */
|
||||
atchFileGroupId: string;
|
||||
}
|
||||
|
||||
/** 설치인가검토의견 저장 요청 DTO */
|
||||
export interface InstlSncnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase {
|
||||
/** 설치인가문서번호 */
|
||||
instlSncnDocNo: string;
|
||||
}
|
||||
|
||||
/** 설치인가 보완요구 저장 DTO(배열 원소) */
|
||||
export type InstlSncnSplmntDmndSaveDto = SplmntDmndSaveBase;
|
||||
|
||||
/** 설치인가 보완요구 응답 DTO */
|
||||
export type InstlSncnSplmntDmndResponseDto = InstlSncnSplmntDmndSaveDto &
|
||||
SplmntDmndResponseExtension & {
|
||||
instlSncnDocNo?: string;
|
||||
};
|
||||
|
||||
/** 설치인가 신청 - 통합 저장 요청 DTO */
|
||||
export interface InstlSncnApplySaveRequestDto {
|
||||
instlSncnMng: InstlSncnMng;
|
||||
bzScaleList: InstlSncnBzScaleSaveDto[];
|
||||
bizCstList: InstlSncnBizCstSaveRequestDto[];
|
||||
cklstResultList: InstlSncnCklstResultSaveDto[];
|
||||
esntlAtchFileList: InstlSncnEsntlAtchFileSaveDto[];
|
||||
export interface InstlSncnApplySaveRequestDto extends PbadmsApplySaveHeader {
|
||||
/** 설치인가관리 정보 */
|
||||
instlSncnMng: InstlSncnMngSaveRequestDto;
|
||||
/** 사업규모 목록 */
|
||||
bzScaleList?: InstlSncnBzScaleSaveDto[];
|
||||
/** 총사업비 목록 */
|
||||
bizCstList?: InstlSncnBizCstSaveRequestDto[];
|
||||
/** 체크리스트 검토결과 목록 */
|
||||
cklstResultList?: InstlSncnCklstResultSaveDto[];
|
||||
/** 체크리스트 검토결과 맵 (rvwStpComCd -> 목록) */
|
||||
cklstResultMap?: Record<string, InstlSncnCklstResultSaveDto[]>;
|
||||
/** 필수첨부파일 목록 */
|
||||
esntlAtchFileList?: InstlSncnEsntlAtchFileSaveDto[];
|
||||
/** 검토의견 목록 */
|
||||
rvwOpnnList?: InstlSncnRvwOpnnSaveRequestDto[];
|
||||
/** 보완요구 목록 */
|
||||
splmntDmndList?: InstlSncnSplmntDmndSaveDto[];
|
||||
}
|
||||
|
||||
export type ReviewStepCode = "LOCAL" | "ENV" | "KECO";
|
||||
|
||||
/** 설치인가검토의견 응답 DTO */
|
||||
export type InstlSncnRvwOpnnResponseDto = InstlSncnRvwOpnnSaveRequestDto &
|
||||
RvwOpnnResponseExtension & {
|
||||
instlSncnDocNo: string;
|
||||
rvwOpnnSn: number;
|
||||
opnrSeComCd: string;
|
||||
opnrNm: string;
|
||||
rvwRsltCn: string;
|
||||
rvwRsltAnmlyYn: string;
|
||||
atchFileGroupId: string | null;
|
||||
opnrSeCdNm: string | null;
|
||||
regDt: string;
|
||||
};
|
||||
|
||||
|
||||
/** 전자결재 진행상태 (클라이언트 응답용) */
|
||||
export interface ElaprDocPrgrsClientResponseDto {
|
||||
elaprDocNo?: string;
|
||||
elaprDmndId?: string;
|
||||
elaprPrgrsId?: string;
|
||||
atrzSeqNo?: number;
|
||||
atrzSeComCd?: string;
|
||||
atrzSeComNm?: string;
|
||||
aprvrUuid?: string;
|
||||
aprvrUuidNm?: string;
|
||||
atrzPrgrsSttsComCd?: string;
|
||||
atrzPrgrsSttsComNm?: string;
|
||||
rjctRsn?: string | null;
|
||||
atrzDt?: string | null;
|
||||
jbpsSeNm?: string;
|
||||
jbgdSeNm?: string;
|
||||
ognzNm?: string;
|
||||
}
|
||||
|
||||
// det 응답에 전자결재 진행상태 목록을 포함
|
||||
export interface InstlSncnMngDetResponse {
|
||||
detDto: InstlSncnMng;
|
||||
bizSclList: any[];
|
||||
bizCstList: any[];
|
||||
cklstResultList?: any[];
|
||||
esntlFileList: any[];
|
||||
etcFileList: any[];
|
||||
/** 설치인가관리 상세 응답 DTO */
|
||||
export interface InstlSncnMngDetResponse extends PbadmsMngDetResponseBase<
|
||||
InstlSncnMngResponseDto,
|
||||
InstlSncnRvwOpnnResponseDto,
|
||||
InstlSncnSplmntDmndResponseDto
|
||||
> {
|
||||
detDto: InstlSncnMngResponseDto;
|
||||
bizSclList: BizSclTreeDto[];
|
||||
bizCstList: BizCstTreeDto[];
|
||||
esntlFileList: EsntlAtchFileMngTreeResponseDto[];
|
||||
etcFileList: AtflMngClientResponseDto[];
|
||||
elaprDocPrgrsList?: ElaprDocPrgrsClientResponseDto[] | null;
|
||||
}
|
||||
|
||||
export interface InstlSncnMngService {
|
||||
getPage: (params: InstlSncnMngSearchParams) => Promise<SpringPage<InstlSncnMng>>;
|
||||
getPage: (params: InstlSncnMngSearchParams) => Promise<PageResponse<InstlSncnMngResponseDto>>;
|
||||
getById: (id: string) => Promise<InstlSncnMngDetResponse>;
|
||||
save: (payload: InstlSncnMngSaveRequestDto) => Promise<string>;
|
||||
saveApply: (payload: InstlSncnApplySaveRequestDto) => Promise<string>;
|
||||
update: (id: string, payload: InstlSncnMngUpdateRequestDto) => Promise<void>;
|
||||
techReview: (id: string, payload: InstlSncnApplySaveRequestDto) => Promise<void>;
|
||||
submitSupplement: (id: string, payload: InstlSncnApplySaveRequestDto) => Promise<string>;
|
||||
submitReqSupplement: (id: string, payload: InstlSncnApplySaveRequestDto) => Promise<string>;
|
||||
remove: (id: string) => Promise<void>;
|
||||
changePrgrsSttsComCd: (payload: DocPrgrsUpdateRequestDto) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import type {
|
||||
RvwMnpwMngPageSearchDto,
|
||||
RvwMnpwMngSaveRequestDto,
|
||||
RvwMnpwMngSearchDto,
|
||||
RvwMnpwMngUpdateRequestDto,
|
||||
@@ -10,9 +11,12 @@ import type {
|
||||
|
||||
import { rvwMnpwMngService } from "./rvw-mnpw-service";
|
||||
|
||||
const rvwMnpwMngBaseKey = [ENDPOINT.AS_COMMON_SERVICE.RVW_MNPW] as const;
|
||||
const rvwMnpwMngBaseKey = [ENDPOINT.AS_SERVICE.RVW_MNPW] as const;
|
||||
|
||||
export function useRvwMnpwMngPage(params: RvwMnpwMngSearchDto, options?: { enabled?: boolean }) {
|
||||
export function useRvwMnpwMngPage(
|
||||
params: RvwMnpwMngPageSearchDto,
|
||||
options?: { enabled?: boolean },
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: [...rvwMnpwMngBaseKey, "findPage", params],
|
||||
queryFn: () => rvwMnpwMngService.findPage(params),
|
||||
@@ -20,6 +24,17 @@ export function useRvwMnpwMngPage(params: RvwMnpwMngSearchDto, options?: { enabl
|
||||
});
|
||||
}
|
||||
|
||||
export function useRvwMnpwMngLoginUserList(
|
||||
searchDto: RvwMnpwMngSearchDto,
|
||||
options?: { enabled?: boolean },
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: [...rvwMnpwMngBaseKey, "findRvwLoginUserList", searchDto],
|
||||
queryFn: () => rvwMnpwMngService.findRvwLoginUserList(searchDto),
|
||||
enabled: options?.enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRvwMnpwMng(
|
||||
atrzSeComCd: string | null | undefined,
|
||||
ognzSn: number | null | undefined,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import type { PageResponse } from "@/shared/model";
|
||||
|
||||
import type {
|
||||
RvwMnpwMngPageSearchDto,
|
||||
RvwMnpwMngResponseDto,
|
||||
RvwMnpwMngSaveRequestDto,
|
||||
RvwMnpwMngSearchDto,
|
||||
@@ -10,14 +11,21 @@ import type {
|
||||
RvwMnpwMngUpdateRequestDto,
|
||||
} from "../model/rvw-mnpw-interface";
|
||||
|
||||
const BASE = ENDPOINT.AS_COMMON_SERVICE.RVW_MNPW;
|
||||
const BASE = ENDPOINT.AS_SERVICE.RVW_MNPW;
|
||||
|
||||
function pkPath(atrzSeComCd: string, ognzSn: number, userUuid: string): string {
|
||||
return `${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`;
|
||||
async function findPage(
|
||||
params: RvwMnpwMngPageSearchDto,
|
||||
): Promise<PageResponse<RvwMnpwMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<RvwMnpwMngResponseDto>>(BASE, { params });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function findPage(params: RvwMnpwMngSearchDto): Promise<PageResponse<RvwMnpwMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<RvwMnpwMngResponseDto>>(BASE, { params });
|
||||
async function findRvwLoginUserList(
|
||||
searchDto: RvwMnpwMngSearchDto,
|
||||
): Promise<PageResponse<RvwMnpwMngResponseDto>> {
|
||||
const response = await client.get<PageResponse<RvwMnpwMngResponseDto>>(`${BASE}/loginuser/me`, {
|
||||
params: searchDto,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -26,41 +34,48 @@ async function findById(
|
||||
ognzSn: number,
|
||||
userUuid: string,
|
||||
): Promise<RvwMnpwMngResponseDto> {
|
||||
const response = await client.get<RvwMnpwMngResponseDto>(pkPath(atrzSeComCd, ognzSn, userUuid));
|
||||
const response = await client.get<RvwMnpwMngResponseDto>(
|
||||
`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function save(body: RvwMnpwMngSaveRequestDto): Promise<RvwMnpwMngResponseDto> {
|
||||
const response = await client.post<RvwMnpwMngResponseDto>(BASE, body);
|
||||
async function saveRvwMnpwMng(body: RvwMnpwMngSaveRequestDto): Promise<RvwMnpwMngResponseDto> {
|
||||
const { atrzSeComCd, ognzSn, userUuid } = body;
|
||||
const response = await client.post<RvwMnpwMngResponseDto>(
|
||||
`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function update(
|
||||
async function updateRvwMnpwMng(
|
||||
atrzSeComCd: string,
|
||||
ognzSn: number,
|
||||
userUuid: string,
|
||||
body: RvwMnpwMngUpdateRequestDto,
|
||||
): Promise<RvwMnpwMngResponseDto> {
|
||||
const response = await client.put<RvwMnpwMngResponseDto>(
|
||||
pkPath(atrzSeComCd, ognzSn, userUuid),
|
||||
`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`,
|
||||
body,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function deleteRvwMnpw(
|
||||
async function deleteRvwMnpwMng(
|
||||
atrzSeComCd: string,
|
||||
ognzSn: number,
|
||||
userUuid: string,
|
||||
): Promise<string> {
|
||||
const response = await client.delete<string>(pkPath(atrzSeComCd, ognzSn, userUuid));
|
||||
const response = await client.delete<string>(`${BASE}/${atrzSeComCd}/${ognzSn}/${userUuid}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export const rvwMnpwMngService = {
|
||||
findPage,
|
||||
findRvwLoginUserList,
|
||||
findById,
|
||||
save,
|
||||
update,
|
||||
delete: deleteRvwMnpw,
|
||||
save: saveRvwMnpwMng,
|
||||
update: updateRvwMnpwMng,
|
||||
delete: deleteRvwMnpwMng,
|
||||
} satisfies RvwMnpwMngService;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
export {
|
||||
useDeleteRvwMnpwMng,
|
||||
useRvwMnpwMng,
|
||||
useRvwMnpwMngLoginUserList,
|
||||
useRvwMnpwMngPage,
|
||||
useSaveRvwMnpwMng,
|
||||
useUpdateRvwMnpwMng,
|
||||
} from "./api/queries";
|
||||
export { rvwMnpwMngService } from "./api/rvw-mnpw-service";
|
||||
export type {
|
||||
RvwMnpwMngPageSearchDto,
|
||||
RvwMnpwMngResponseDto,
|
||||
RvwMnpwMngSaveRequestDto,
|
||||
RvwMnpwMngSearchDto,
|
||||
|
||||
@@ -14,13 +14,16 @@ export interface RvwMnpwMngResponseDto {
|
||||
regDt?: string;
|
||||
}
|
||||
|
||||
/** 백엔드 RvwMnpwMngSearchDto + Pageable */
|
||||
export interface RvwMnpwMngSearchDto extends PageableParams {
|
||||
/** 백엔드 RvwMnpwMngSearchDto */
|
||||
export interface RvwMnpwMngSearchDto {
|
||||
atrzSeComCd?: string;
|
||||
ognzSn?: number;
|
||||
userUuid?: string;
|
||||
}
|
||||
|
||||
/** findPage — RvwMnpwMngSearchDto + Pageable */
|
||||
export type RvwMnpwMngPageSearchDto = RvwMnpwMngSearchDto & PageableParams;
|
||||
|
||||
/** 백엔드 RvwMnpwMngSaveRequestDto */
|
||||
export interface RvwMnpwMngSaveRequestDto {
|
||||
atrzSeComCd: string;
|
||||
@@ -35,7 +38,10 @@ export interface RvwMnpwMngUpdateRequestDto {
|
||||
|
||||
/** Controller public 메서드명·시그니처와 동일 */
|
||||
export interface RvwMnpwMngService {
|
||||
findPage(params: RvwMnpwMngSearchDto): Promise<PageResponse<RvwMnpwMngResponseDto>>;
|
||||
findPage(params: RvwMnpwMngPageSearchDto): Promise<PageResponse<RvwMnpwMngResponseDto>>;
|
||||
findRvwLoginUserList(
|
||||
searchDto: RvwMnpwMngSearchDto,
|
||||
): Promise<PageResponse<RvwMnpwMngResponseDto>>;
|
||||
findById(atrzSeComCd: string, ognzSn: number, userUuid: string): Promise<RvwMnpwMngResponseDto>;
|
||||
save(body: RvwMnpwMngSaveRequestDto): Promise<RvwMnpwMngResponseDto>;
|
||||
update(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { handleTusUpload, type TusUploadOptions } from "./lib/utils";
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Upload } from "tus-js-client";
|
||||
|
||||
import { ENDPOINT, PROXY_HOST } from "@/shared/config";
|
||||
import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config/env";
|
||||
import { getCookie } from "@/shared/lib";
|
||||
|
||||
interface TusUploadOptions {
|
||||
metadata?: Record<string, string>;
|
||||
handleUploadStart?: (upload: Upload) => void;
|
||||
handleProgress?: (percentage: number) => void;
|
||||
handleSuccess?: (upload: Upload, atflCd: string, atflId: string) => void;
|
||||
handleError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
function handleTusUpload(file: File, options?: TusUploadOptions) {
|
||||
let atflCd = "";
|
||||
let atflId = "";
|
||||
|
||||
const metadata = {
|
||||
filename: file.name,
|
||||
filetype: file.type,
|
||||
...options?.metadata,
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
endpoint: `${PROXY_HOST}${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`,
|
||||
chunkSize: 10 * 1024 * 1024,
|
||||
headers: {
|
||||
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
|
||||
[AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "",
|
||||
},
|
||||
metadata,
|
||||
onError(error) {
|
||||
console.error("TUS upload failed:", error);
|
||||
options?.handleError?.(error);
|
||||
},
|
||||
onProgress(bytesUploaded, bytesTotal) {
|
||||
const percentage = (bytesUploaded / bytesTotal) * 100;
|
||||
options?.handleProgress?.(percentage);
|
||||
},
|
||||
onAfterResponse(_req, res) {
|
||||
const fileInfo = res.getHeader("X-File-Info");
|
||||
if (!fileInfo) return;
|
||||
|
||||
const parsed = JSON.parse(fileInfo) as { atflCd?: string; atflId?: string };
|
||||
atflCd = parsed.atflCd ?? "";
|
||||
atflId = parsed.atflId ?? "";
|
||||
},
|
||||
onSuccess() {
|
||||
options?.handleSuccess?.(upload, atflCd, atflId);
|
||||
},
|
||||
});
|
||||
|
||||
upload.start();
|
||||
options?.handleUploadStart?.(upload);
|
||||
|
||||
return upload;
|
||||
}
|
||||
|
||||
export { handleTusUpload };
|
||||
export type { TusUploadOptions };
|
||||
@@ -1 +0,0 @@
|
||||
export { AcntgCodeManagementPage } from "./ui/acntg-code-management-page";
|
||||
@@ -1,245 +0,0 @@
|
||||
import type { AcntgSeMngResponseDto } from "@/entities/acntg";
|
||||
|
||||
import type {
|
||||
AcntgDisplaySlot,
|
||||
AcntgSeBaseEntry,
|
||||
AcntgSeGridState,
|
||||
} from "../model/acntg-se-grid-types";
|
||||
import type { AcntgSeRow } from "../model/acntg-se-row-types";
|
||||
import { buildAcntgDisplayRows } from "../model/build-acntg-display-rows";
|
||||
|
||||
export function createLoadedRowKey(index: number, acntgSeCd: string): string {
|
||||
return `loaded-${index}-${acntgSeCd}`;
|
||||
}
|
||||
|
||||
export function createDraftRowKey(): string {
|
||||
return `draft-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||
}
|
||||
|
||||
export function createEmptyGridState(): AcntgSeGridState {
|
||||
return {
|
||||
baseByKey: {},
|
||||
structure: [],
|
||||
drafts: {},
|
||||
patches: {},
|
||||
deletedKeys: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
function toInitialRow(entry: AcntgSeBaseEntry): AcntgSeRow {
|
||||
return {
|
||||
...entry,
|
||||
isNew: false,
|
||||
isDirty: false,
|
||||
isDeleted: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildStructureFromBaseRows(
|
||||
baseByKey: Record<string, AcntgSeBaseEntry>,
|
||||
): AcntgDisplaySlot[] {
|
||||
const rows = Object.values(baseByKey).map(toInitialRow);
|
||||
const display = buildAcntgDisplayRows(rows);
|
||||
const cdToRowKey = new Map(rows.map((row) => [row.acntgSeCd, row.rowKey]));
|
||||
|
||||
return display.map(({ row, depth }) => ({
|
||||
rowKey: row.rowKey,
|
||||
depth,
|
||||
parentRowKey: row.upAcntgSeCd?.trim() ? (cdToRowKey.get(row.upAcntgSeCd.trim()) ?? null) : null,
|
||||
}));
|
||||
}
|
||||
|
||||
export function hydrateGridFromServer(content: AcntgSeMngResponseDto[]): AcntgSeGridState {
|
||||
const baseByKey: Record<string, AcntgSeBaseEntry> = {};
|
||||
|
||||
content.forEach((item, index) => {
|
||||
const rowKey = createLoadedRowKey(index, item.acntgSeCd);
|
||||
baseByKey[rowKey] = { ...item, rowKey };
|
||||
});
|
||||
|
||||
return {
|
||||
baseByKey,
|
||||
structure: buildStructureFromBaseRows(baseByKey),
|
||||
drafts: {},
|
||||
patches: {},
|
||||
deletedKeys: new Set(),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveParentAcntgSeCd(
|
||||
state: AcntgSeGridState,
|
||||
parentRowKey: string | null,
|
||||
): string | undefined {
|
||||
if (!parentRowKey) return undefined;
|
||||
const parent = mergeAcntgRow(state, parentRowKey);
|
||||
const cd = parent?.acntgSeCd?.trim();
|
||||
return cd || undefined;
|
||||
}
|
||||
|
||||
export function mergeAcntgRow(state: AcntgSeGridState, rowKey: string): AcntgSeRow | null {
|
||||
const draft = state.drafts[rowKey];
|
||||
if (draft) {
|
||||
return {
|
||||
rowKey,
|
||||
acntgSeCd: draft.acntgSeCd,
|
||||
acntgSeCdNm: draft.acntgSeCdNm,
|
||||
expln: draft.expln,
|
||||
useYn: draft.useYn,
|
||||
cdLvNo: draft.cdLvNo,
|
||||
upAcntgSeCd: resolveParentAcntgSeCd(state, draft.parentRowKey),
|
||||
isNew: true,
|
||||
isDirty: true,
|
||||
isDeleted: false,
|
||||
};
|
||||
}
|
||||
|
||||
const base = state.baseByKey[rowKey];
|
||||
if (!base) return null;
|
||||
|
||||
const patch = state.patches[rowKey] ?? {};
|
||||
const hasPatch = Object.keys(patch).length > 0;
|
||||
|
||||
return {
|
||||
...base,
|
||||
...patch,
|
||||
rowKey,
|
||||
isNew: false,
|
||||
isDirty: hasPatch,
|
||||
isDeleted: state.deletedKeys.has(rowKey),
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeAllAcntgRows(state: AcntgSeGridState): AcntgSeRow[] {
|
||||
const keys = new Set([...Object.keys(state.baseByKey), ...Object.keys(state.drafts)]);
|
||||
|
||||
return [...keys]
|
||||
.map((rowKey) => mergeAcntgRow(state, rowKey))
|
||||
.filter((row): row is AcntgSeRow => row != null);
|
||||
}
|
||||
|
||||
export function hasAcntgGridPendingChanges(state: AcntgSeGridState): boolean {
|
||||
return (
|
||||
state.deletedKeys.size > 0 ||
|
||||
Object.keys(state.drafts).length > 0 ||
|
||||
Object.keys(state.patches).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
export function collectDescendantRowKeys(
|
||||
structure: AcntgDisplaySlot[],
|
||||
rootRowKey: string,
|
||||
): Set<string> {
|
||||
const result = new Set<string>([rootRowKey]);
|
||||
const queue = [rootRowKey];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const parentKey = queue.shift()!;
|
||||
for (const slot of structure) {
|
||||
if (slot.parentRowKey === parentKey && !result.has(slot.rowKey)) {
|
||||
result.add(slot.rowKey);
|
||||
queue.push(slot.rowKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function insertRootSlot(
|
||||
structure: AcntgDisplaySlot[],
|
||||
slot: AcntgDisplaySlot,
|
||||
): AcntgDisplaySlot[] {
|
||||
const firstRootIndex = structure.findIndex((item) => item.parentRowKey === null);
|
||||
if (firstRootIndex === -1) return [slot, ...structure];
|
||||
return [...structure.slice(0, firstRootIndex), slot, ...structure.slice(firstRootIndex)];
|
||||
}
|
||||
|
||||
export function insertChildSlot(
|
||||
structure: AcntgDisplaySlot[],
|
||||
parentRowKey: string,
|
||||
slot: AcntgDisplaySlot,
|
||||
): AcntgDisplaySlot[] {
|
||||
const parentIndex = structure.findIndex((item) => item.rowKey === parentRowKey);
|
||||
if (parentIndex === -1) return [...structure, slot];
|
||||
|
||||
const insertAt = parentIndex + 1;
|
||||
return [...structure.slice(0, insertAt), slot, ...structure.slice(insertAt)];
|
||||
}
|
||||
|
||||
export function removeSlotsFromStructure(
|
||||
structure: AcntgDisplaySlot[],
|
||||
rowKeysToRemove: Set<string>,
|
||||
): AcntgDisplaySlot[] {
|
||||
return structure.filter((slot) => !rowKeysToRemove.has(slot.rowKey));
|
||||
}
|
||||
|
||||
export function patchAcntgGridRow(
|
||||
state: AcntgSeGridState,
|
||||
rowKey: string,
|
||||
patch: Partial<AcntgSeRow>,
|
||||
): AcntgSeGridState {
|
||||
const draft = state.drafts[rowKey];
|
||||
if (draft) {
|
||||
return {
|
||||
...state,
|
||||
drafts: {
|
||||
...state.drafts,
|
||||
[rowKey]: {
|
||||
...draft,
|
||||
...(patch.acntgSeCd !== undefined ? { acntgSeCd: patch.acntgSeCd } : {}),
|
||||
...(patch.acntgSeCdNm !== undefined ? { acntgSeCdNm: patch.acntgSeCdNm ?? "" } : {}),
|
||||
...(patch.expln !== undefined ? { expln: patch.expln ?? "" } : {}),
|
||||
...(patch.useYn !== undefined ? { useYn: patch.useYn } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const { rowKey: _rk, isNew: _n, isDirty: _d, isDeleted: _del, ...dtoPatch } = patch;
|
||||
|
||||
return {
|
||||
...state,
|
||||
patches: {
|
||||
...state.patches,
|
||||
[rowKey]: { ...state.patches[rowKey], ...dtoPatch },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function markAcntgRowsDeleted(
|
||||
state: AcntgSeGridState,
|
||||
rootRowKey: string,
|
||||
): AcntgSeGridState {
|
||||
const keysToDelete = collectDescendantRowKeys(state.structure, rootRowKey);
|
||||
const deletedKeys = new Set(state.deletedKeys);
|
||||
for (const key of keysToDelete) {
|
||||
deletedKeys.add(key);
|
||||
}
|
||||
return { ...state, deletedKeys };
|
||||
}
|
||||
|
||||
export function validateDraftParentAcntgSeCd(
|
||||
state: AcntgSeGridState,
|
||||
rowKey: string,
|
||||
): string | null {
|
||||
const draft = state.drafts[rowKey];
|
||||
if (!draft?.parentRowKey) return null;
|
||||
|
||||
const parent = mergeAcntgRow(state, draft.parentRowKey);
|
||||
if (!parent?.acntgSeCd?.trim()) {
|
||||
return "하위 항목을 추가하려면 상위 코드를 먼저 입력하세요.";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function removeDraftFromGrid(state: AcntgSeGridState, rowKey: string): AcntgSeGridState {
|
||||
const { [rowKey]: _removed, ...restDrafts } = state.drafts;
|
||||
const keysToRemove = collectDescendantRowKeys(state.structure, rowKey);
|
||||
|
||||
return {
|
||||
...state,
|
||||
drafts: restDrafts,
|
||||
structure: removeSlotsFromStructure(state.structure, keysToRemove),
|
||||
};
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { ACNTG_SE_SAVE_MODE, type AcntgSeMngSaveRequestDto } from "@/entities/acntg";
|
||||
|
||||
import type { AcntgSeRow } from "../model/acntg-se-row-types";
|
||||
|
||||
import { getVisibleRows } from "./row-state";
|
||||
|
||||
function compareCdLvNoDesc(a: AcntgSeRow, b: AcntgSeRow): number {
|
||||
return (b.cdLvNo ?? 1) - (a.cdLvNo ?? 1);
|
||||
}
|
||||
|
||||
function compareCdLvNoAsc(a: AcntgSeRow, b: AcntgSeRow): number {
|
||||
return (a.cdLvNo ?? 1) - (b.cdLvNo ?? 1);
|
||||
}
|
||||
|
||||
export function buildSaveMultiPayload(rows: AcntgSeRow[]): AcntgSeMngSaveRequestDto[] {
|
||||
const payload: AcntgSeMngSaveRequestDto[] = [];
|
||||
|
||||
const deletedRows = rows.filter((row) => row.isDeleted && !row.isNew).sort(compareCdLvNoDesc);
|
||||
|
||||
for (const row of deletedRows) {
|
||||
payload.push({
|
||||
mode: ACNTG_SE_SAVE_MODE.DELETE,
|
||||
acntgSeCd: row.acntgSeCd,
|
||||
acntgSeCdNm: row.acntgSeCdNm?.trim() || "",
|
||||
upAcntgSeCd: row.upAcntgSeCd,
|
||||
cdLvNo: row.cdLvNo,
|
||||
useYn: row.useYn,
|
||||
expln: row.expln?.trim() || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(rows).sort(compareCdLvNoAsc);
|
||||
|
||||
for (const row of visibleRows) {
|
||||
if (row.isNew) {
|
||||
payload.push({
|
||||
mode: ACNTG_SE_SAVE_MODE.CREATE,
|
||||
acntgSeCd: row.acntgSeCd.trim(),
|
||||
acntgSeCdNm: row.acntgSeCdNm.trim(),
|
||||
upAcntgSeCd: row.upAcntgSeCd,
|
||||
cdLvNo: row.cdLvNo,
|
||||
useYn: row.useYn,
|
||||
expln: row.expln?.trim() || undefined,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (row.isDirty) {
|
||||
payload.push({
|
||||
mode: ACNTG_SE_SAVE_MODE.UPDATE,
|
||||
acntgSeCd: row.acntgSeCd.trim(),
|
||||
acntgSeCdNm: row.acntgSeCdNm.trim(),
|
||||
upAcntgSeCd: row.upAcntgSeCd,
|
||||
cdLvNo: row.cdLvNo,
|
||||
useYn: row.useYn,
|
||||
expln: row.expln?.trim() || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { AcntgSeRow } from "../model/acntg-se-row-types";
|
||||
|
||||
export function getVisibleRows(rows: AcntgSeRow[]): AcntgSeRow[] {
|
||||
return rows.filter((row) => !row.isDeleted);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user