Skip to content

Commit 670e7ca

Browse files
authored
[feat] Add EventExtract component with multi-region service extraction and form enhancements (#174)
1 parent feef5a8 commit 670e7ca

File tree

7 files changed

+1050
-1266
lines changed

7 files changed

+1050
-1266
lines changed

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@fluentui/react-components": "^9.64.0",
1515
"@telekom/scale-components": "3.0.0-beta.155",
1616
"@telekom/scale-components-react": "3.0.0-beta.155",
17-
"ahooks": "^3.8.4",
17+
"ahooks": "^3.8.5",
1818
"dayjs": "^1.11.13",
1919
"idb": "^8.0.3",
2020
"jwt-decode": "^4.0.0",
@@ -31,22 +31,22 @@
3131
"@farmfe/core": "^1.7.5",
3232
"@farmfe/js-plugin-postcss": "^1.12.0",
3333
"@farmfe/plugin-react": "^1.2.6",
34-
"@tailwindcss/postcss": "^4.1.7",
34+
"@tailwindcss/postcss": "^4.1.8",
3535
"@testing-library/react": "^16.3.0",
36-
"@types/lodash": "^4.17.16",
37-
"@types/node": "^22.15.18",
38-
"@types/react": "^19.1.4",
36+
"@types/lodash": "^4.17.17",
37+
"@types/node": "^22.15.24",
38+
"@types/react": "^19.1.6",
3939
"@types/react-dom": "^19.1.5",
4040
"@types/react-helmet": "^6.1.11",
4141
"autoprefixer": "^10.4.21",
4242
"core-js": "^3.42.0",
43-
"eslint": "^9.26.0",
44-
"globals": "^16.1.0",
45-
"postcss": "^8.5.3",
43+
"eslint": "^9.27.0",
44+
"globals": "^16.2.0",
45+
"postcss": "^8.5.4",
4646
"react-refresh": "^0.17.0",
4747
"tailwindcss": "3.4.17",
4848
"tailwindcss-scoped-preflight": "^3.4.12",
49-
"typescript-eslint": "^8.32.1"
49+
"typescript-eslint": "^8.33.0"
5050
},
5151
"packageManager": "[email protected]"
5252
}

pnpm-lock.yaml

Lines changed: 860 additions & 1254 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Event/EventCard.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Indicator } from "../Home/Indicator";
66
import { EventStatus, EventType } from "./Enums";
77
import { EventAffected } from "./EventAffected";
88
import { EventEditor } from "./EventEditor";
9+
import { EventExtract } from "./EventExtract";
910

1011
/**
1112
* Represents an EventCard component that displays detailed information about a specific event.
@@ -37,7 +38,13 @@ export function EventCard({ Event }: { Event: Models.IEvent }) {
3738
</div>
3839

3940
<Authorized>
40-
<EventEditor Event={Event} />
41+
<div className="flex gap-x-3">
42+
{
43+
Event.RegionServices.size > 1 &&
44+
<EventExtract Event={Event} />
45+
}
46+
<EventEditor Event={Event} />
47+
</div>
4148
</Authorized>
4249
</div>
4350

src/Components/Event/EventExtract.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { ScaleButton, ScaleHelperText, ScaleIconActionExport, ScaleModal, ScaleTable } from "@telekom/scale-components-react";
2+
import { useBoolean } from "ahooks";
3+
import { chain } from "lodash";
4+
import { Models } from "~/Services/Status.Models";
5+
import { useEventExtract } from "./useEventExtract";
6+
7+
/**
8+
* @author Aloento
9+
* @since 1.0.0
10+
* @version 0.1.0
11+
*/
12+
export function EventExtract({ Event }: { Event: Models.IEvent }) {
13+
const { services, setServices, valServices, OnSubmit, Loading } = useEventExtract(Event);
14+
const [open, { setTrue, setFalse }] = useBoolean();
15+
16+
return <>
17+
<ScaleButton onClick={setTrue} size="small" variant="secondary">
18+
<ScaleIconActionExport />
19+
Extract
20+
</ScaleButton>
21+
22+
<ScaleModal
23+
heading="Extract Event"
24+
opened={open}
25+
omitCloseButton
26+
size="small"
27+
class="absolute"
28+
onScale-before-close={(e) => e.preventDefault()}
29+
>
30+
<form
31+
className="flex flex-col gap-y-6"
32+
autoComplete="off"
33+
onSubmit={(e) => {
34+
e.preventDefault();
35+
OnSubmit().then((ok) => ok && setFalse());
36+
}}>
37+
<ScaleTable>
38+
<div className="max-h-96 overflow-auto">
39+
<table>
40+
<thead className="sticky">
41+
<tr>
42+
<th />
43+
<th>Service Name</th>
44+
<th>Region</th>
45+
</tr>
46+
</thead>
47+
48+
<tbody>
49+
{chain([...Event.RegionServices])
50+
.orderBy(x => x.Service.Name)
51+
.map((x, i) =>
52+
<tr key={i}>
53+
<td>
54+
<input
55+
type="checkbox"
56+
onChange={(e) => {
57+
const checked = e.target.checked;
58+
setServices((curr) => {
59+
if (checked) {
60+
return [...curr, x];
61+
}
62+
63+
return curr.filter(s => s !== x);
64+
})
65+
}}
66+
/>
67+
</td>
68+
69+
<td>{x.Service.Name}</td>
70+
<td>{x.Region.Name}</td>
71+
</tr>)
72+
.value()}
73+
</tbody>
74+
</table>
75+
</div>
76+
77+
<ScaleHelperText
78+
variant={valServices ? "danger" : "informational"}
79+
helperText={valServices || `Selected ${services.length} services`}
80+
/>
81+
</ScaleTable>
82+
83+
<div className="flex gap-x-3 self-end">
84+
<ScaleButton onClick={setFalse} variant="secondary" type="button">
85+
Cancel
86+
</ScaleButton>
87+
88+
<ScaleButton type="submit" disabled={Loading}>
89+
Submit
90+
</ScaleButton>
91+
</div>
92+
</form>
93+
</ScaleModal>
94+
</>;
95+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useRequest } from "ahooks";
2+
import { useState } from "react";
3+
import { useStatus } from "~/Services/Status";
4+
import { Models } from "~/Services/Status.Models";
5+
import { useAccessToken } from "../Auth/useAccessToken";
6+
import { useRouter } from "../Router";
7+
8+
/**
9+
* @author Aloento
10+
* @since 1.0.0
11+
* @version 0.1.0
12+
*/
13+
export function useEventExtract(event: Models.IEvent) {
14+
const [services, _setServices] = useState<Models.IRegionService[]>([]);
15+
const [valServices, setValServices] = useState<string>();
16+
function setServices(action: (curr: Models.IRegionService[]) => Models.IRegionService[] = (s) => s) {
17+
const updated = action(services);
18+
let err: boolean = false;
19+
20+
if (!updated || !updated.length) {
21+
setValServices("At least one service is required.");
22+
err = true;
23+
}
24+
25+
_setServices(updated);
26+
!err && setValServices(undefined);
27+
28+
return !err;
29+
}
30+
31+
const getToken = useAccessToken();
32+
const { Nav } = useRouter();
33+
const { Refresh } = useStatus();
34+
35+
const { runAsync, loading } = useRequest(async () => {
36+
if (!setServices()) {
37+
return false;
38+
}
39+
40+
const url = process.env.SD_BACKEND_URL!;
41+
42+
const body: Record<string, any> = {
43+
components: services.map(s => s.Id),
44+
}
45+
46+
const raw = await fetch(`${url}/v2/incidents/${event.Id}/extract`, {
47+
method: "POST",
48+
headers: {
49+
"Content-Type": "application/json",
50+
"Authorization": `Bearer ${getToken()}`
51+
},
52+
body: JSON.stringify(body)
53+
});
54+
55+
const res = await raw.json();
56+
const id = res.id;
57+
58+
if (id) {
59+
await Refresh();
60+
Nav(`/Event/${id}`);
61+
}
62+
63+
return true;
64+
}, {
65+
manual: true
66+
});
67+
68+
return {
69+
services,
70+
setServices,
71+
valServices,
72+
OnSubmit: runAsync,
73+
Loading: loading
74+
}
75+
}

src/Components/New/useNewForm.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export function useNewForm() {
150150
const getToken = useAccessToken();
151151

152152
const { runAsync, loading } = useRequest(async () => {
153-
if (![setTitle(), setType(), setDescription(), setStart, setEnd(), setServices()].every(Boolean)) {
153+
if (![setTitle(), setType(), setDescription(), setStart(), setEnd(), setServices()].every(Boolean)) {
154154
return;
155155
}
156156

src/Services/Status.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const db = new DB(EmptyDB);
3636
interface IContext {
3737
DB: IStatusContext;
3838
Update: (data?: IStatusContext) => void;
39+
Refresh: () => Promise<unknown>;
3940
}
4041

4142
const CTX = createContext<IContext>({} as IContext);
@@ -138,6 +139,6 @@ export function StatusContext({ children }: { children: JSX.Element }) {
138139
}
139140

140141
return (
141-
<CTX.Provider value={{ DB: ins, Update: update }}>{children}</CTX.Provider>
142+
<CTX.Provider value={{ DB: ins, Update: update, Refresh: runAsync }}>{children}</CTX.Provider>
142143
);
143144
}

0 commit comments

Comments
 (0)