Skip to content

Commit 64cf7ac

Browse files
committed
feat: enhance DeviceDetail and DeviceList components with improved UI and method execution functionality
1 parent d241f58 commit 64cf7ac

2 files changed

Lines changed: 195 additions & 85 deletions

File tree

src/features/DeviceDetail.tsx

Lines changed: 165 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useState } from "react";
2+
import { Button, Form, Modal } from "react-bootstrap";
13
import {
24
DeviceFeedbacks,
35
DeviceMethods,
@@ -6,6 +8,7 @@ import {
68
useGetDeviceFeedbacksQuery,
79
useGetDeviceMethodsQuery,
810
useGetDevicePropertiesQuery,
11+
useSetDeviceJsonCommandMutation,
912
} from "../store/apiSlice";
1013

1114
const DeviceDetail = ({ item }: DeviceDetailProps) => {
@@ -30,6 +33,7 @@ const DeviceDetail = ({ item }: DeviceDetailProps) => {
3033
properties={properties}
3134
methods={methods}
3235
feedbacks={feedbacks}
36+
deviceKey={item.Key}
3337
/>
3438
);
3539
};
@@ -44,80 +48,183 @@ const DeviceDetailRender = ({
4448
properties,
4549
methods,
4650
feedbacks,
51+
deviceKey,
4752
}: DeviceDetailRenderProps) => {
53+
const [selectedMethod, setSelectedMethod] = useState<DeviceMethods | null>(null);
54+
const [paramValues, setParamValues] = useState<Record<string, string>>({});
55+
const [executeMethod, { isLoading: isExecuting }] = useSetDeviceJsonCommandMutation();
56+
57+
const handleOpen = (method: DeviceMethods) => {
58+
setSelectedMethod(method);
59+
setParamValues(Object.fromEntries(method.Params.map((p) => [p.Name, ""])));
60+
};
61+
62+
const handleClose = () => {
63+
setSelectedMethod(null);
64+
setParamValues({});
65+
};
66+
67+
const handleExecute = async () => {
68+
if (!selectedMethod) return;
69+
await executeMethod({ deviceKey, methodName: selectedMethod.Name, params: paramValues });
70+
handleClose();
71+
};
72+
4873
return (
4974
<>
5075
<h2>Device Detail</h2>
5176
<div className="h-100 d-flex flex-column overflow-auto">
5277
<h3>Properties</h3>
53-
<ul>
54-
{properties.map((p) => (
55-
<li key={p.Name}>
56-
{p.Name} ({p.Type}) - Value: {p.Value} - CanRead:{" "}
57-
{p.CanRead ? "Yes" : "No"} - CanWrite: {p.canWrite ? "Yes" : "No"}
58-
</li>
59-
))}
60-
</ul>
78+
<table className="table table-sm table-striped">
79+
<thead>
80+
<tr>
81+
<th>Name</th>
82+
<th>Type</th>
83+
<th>Value</th>
84+
<th>Can Read</th>
85+
<th>Can Write</th>
86+
</tr>
87+
</thead>
88+
<tbody>
89+
{properties.map((p) => (
90+
<tr key={p.Name}>
91+
<td>{p.Name}</td>
92+
<td>{p.Type}</td>
93+
<td>{p.Value}</td>
94+
<td>{p.CanRead ? "Yes" : "No"}</td>
95+
<td>{p.canWrite ? "Yes" : "No"}</td>
96+
</tr>
97+
))}
98+
</tbody>
99+
</table>
61100

62101
<h3>Methods</h3>
63-
<ul>
64-
{methods.map((m) => (
65-
<li key={m.Name}>
66-
{m.Name} - Params:{" "}
67-
{m.Params.map((param) => `${param.Name}: ${param.Type}`).join(
68-
", ",
69-
)}
70-
</li>
71-
))}
72-
</ul>
102+
<table className="table table-sm table-striped">
103+
<thead>
104+
<tr>
105+
<th>Name</th>
106+
<th>Params</th>
107+
<th></th>
108+
</tr>
109+
</thead>
110+
<tbody>
111+
{methods.map((m) => (
112+
<tr key={m.Name}>
113+
<td>{m.Name}</td>
114+
<td>
115+
{m.Params.map((param) => `${param.Name}: ${param.Type}`).join(", ")}
116+
</td>
117+
<td>
118+
<button className="btn btn-sm btn-primary" onClick={() => handleOpen(m)}>Execute</button>
119+
</td>
120+
</tr>
121+
))}
122+
</tbody>
123+
</table>
124+
125+
<Modal show={!!selectedMethod} onHide={handleClose}>
126+
<Modal.Header closeButton>
127+
<Modal.Title>Execute: {selectedMethod?.Name}</Modal.Title>
128+
</Modal.Header>
129+
<Modal.Body>
130+
{selectedMethod?.Params.length === 0 ? (
131+
<p>This method has no parameters.</p>
132+
) : (
133+
<Form>
134+
{selectedMethod?.Params.map((param) => (
135+
<Form.Group key={param.Name} className="mb-3">
136+
<Form.Label>
137+
{param.Name} <small className="text-muted">({param.Type})</small>
138+
</Form.Label>
139+
<Form.Control
140+
type="text"
141+
placeholder={param.Type}
142+
value={paramValues[param.Name] ?? ""}
143+
onChange={(e) =>
144+
setParamValues((prev) => ({ ...prev, [param.Name]: e.target.value }))
145+
}
146+
/>
147+
</Form.Group>
148+
))}
149+
</Form>
150+
)}
151+
</Modal.Body>
152+
<Modal.Footer>
153+
<Button variant="secondary" onClick={handleClose}>Cancel</Button>
154+
<Button variant="primary" onClick={handleExecute} disabled={isExecuting}>
155+
{isExecuting ? "Executing…" : "Execute"}
156+
</Button>
157+
</Modal.Footer>
158+
</Modal>
73159

74160
<h3>Feedbacks</h3>
75161
{feedbacks && (
76162
<>
77163
<h4>Boolean</h4>
78-
<ul>
79-
{feedbacks.BoolValues.length > 0 ? (
80-
<>
81-
{feedbacks.BoolValues.map((f) => (
82-
<li key={f.FeedbackKey}>
83-
{f.FeedbackKey} - Value: {f.Value}
84-
</li>
85-
))}
86-
</>
87-
) : (
88-
<li>None</li>
89-
)}
90-
</ul>
164+
<table className="table table-sm table-striped">
165+
<thead>
166+
<tr>
167+
<th>Key</th>
168+
<th>Value</th>
169+
</tr>
170+
</thead>
171+
<tbody>
172+
{feedbacks.BoolValues.length > 0 ? (
173+
feedbacks.BoolValues.map((f) => (
174+
<tr key={f.FeedbackKey}>
175+
<td>{f.FeedbackKey}</td>
176+
<td>{String(f.Value)}</td>
177+
</tr>
178+
))
179+
) : (
180+
<tr><td colSpan={2}>None</td></tr>
181+
)}
182+
</tbody>
183+
</table>
91184

92185
<h4>Integer</h4>
93-
<ul>
94-
{feedbacks.IntValues.length > 0 ? (
95-
<>
96-
{feedbacks.IntValues.map((f) => (
97-
<li key={f.FeedbackKey}>
98-
{f.FeedbackKey} - Value: {f.Value}
99-
</li>
100-
))}
101-
</>
102-
) : (
103-
<li>None</li>
104-
)}
105-
</ul>
186+
<table className="table table-sm table-striped">
187+
<thead>
188+
<tr>
189+
<th>Key</th>
190+
<th>Value</th>
191+
</tr>
192+
</thead>
193+
<tbody>
194+
{feedbacks.IntValues.length > 0 ? (
195+
feedbacks.IntValues.map((f) => (
196+
<tr key={f.FeedbackKey}>
197+
<td>{f.FeedbackKey}</td>
198+
<td>{f.Value}</td>
199+
</tr>
200+
))
201+
) : (
202+
<tr><td colSpan={2}>None</td></tr>
203+
)}
204+
</tbody>
205+
</table>
106206

107207
<h4>Serial</h4>
108-
<ul>
109-
{feedbacks.SerialValues.length > 0 ? (
110-
<>
111-
{feedbacks.SerialValues.map((f) => (
112-
<li key={f.FeedbackKey}>
113-
{f.FeedbackKey} - Value: {f.Value}
114-
</li>
115-
))}
116-
</>
117-
) : (
118-
<li>None</li>
119-
)}
120-
</ul>
208+
<table className="table table-sm table-striped">
209+
<thead>
210+
<tr>
211+
<th>Key</th>
212+
<th>Value</th>
213+
</tr>
214+
</thead>
215+
<tbody>
216+
{feedbacks.SerialValues.length > 0 ? (
217+
feedbacks.SerialValues.map((f) => (
218+
<tr key={f.FeedbackKey}>
219+
<td>{f.FeedbackKey}</td>
220+
<td>{f.Value}</td>
221+
</tr>
222+
))
223+
) : (
224+
<tr><td colSpan={2}>None</td></tr>
225+
)}
226+
</tbody>
227+
</table>
121228
</>
122229
)}
123230
</div>
@@ -129,4 +236,5 @@ interface DeviceDetailRenderProps {
129236
properties: DeviceProperties[];
130237
methods: DeviceMethods[];
131238
feedbacks?: DeviceFeedbacks;
239+
deviceKey: string;
132240
}

src/features/deviceList.tsx

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useState } from "react";
2-
import { Col, Container, Row } from "react-bootstrap";
32
import { IKeyed, useGetDevicesQuery } from "../store/apiSlice";
43
import DeviceDetail from "./DeviceDetail";
54

@@ -12,33 +11,36 @@ const DeviceList = () => {
1211
}
1312

1413
return (
15-
<>
16-
<Container fluid className="h-100 overflow-hidden">
17-
<Row className="h-100 overflow-hidden">
18-
<Col md={8} className="h-100 overflow-hidden">
19-
<Row className="w-100 fw-bold">
20-
<Col md={12}>Key</Col>
21-
<Col md={12}>Name</Col>
22-
</Row>
23-
<div className="h-100 overflow-auto">
24-
{devices?.map((i) => (
25-
<Row
26-
key={i.Key}
27-
className={`w-100 cursor-pointer ${selectedDevice?.Key === i.Key ? "bg-secondary text-light" : ""}`}
28-
onClick={() => setSelectedDevice(i)}
29-
>
30-
<Col md={12}>{i.Key}</Col>
31-
<Col md={12}>{i.Name}</Col>
32-
</Row>
33-
))}
34-
</div>
35-
</Col>
36-
<Col md={16} className='h-100 overflow-hidden'>
37-
{selectedDevice && <DeviceDetail item={selectedDevice} />}
38-
</Col>
39-
</Row>
40-
</Container>
41-
</>
14+
<div className="h-100 overflow-hidden d-flex gap-3">
15+
<div className="h-100 overflow-hidden d-flex flex-column" style={{ minWidth: 0, flex: "0 0 33%" }}>
16+
<div className="h-100 overflow-auto">
17+
<h2>Devices</h2>
18+
<table className="table table-sm table-striped table-hover">
19+
<thead>
20+
<tr>
21+
<th>Key</th>
22+
<th>Name</th>
23+
</tr>
24+
</thead>
25+
<tbody>
26+
{devices.map((i) => (
27+
<tr
28+
key={i.Key}
29+
className={`cursor-pointer${selectedDevice?.Key === i.Key ? " table-active" : ""}`}
30+
onClick={() => setSelectedDevice(i)}
31+
>
32+
<td>{i.Key}</td>
33+
<td>{i.Name}</td>
34+
</tr>
35+
))}
36+
</tbody>
37+
</table>
38+
</div>
39+
</div>
40+
<div className="h-100 overflow-hidden flex-fill">
41+
{selectedDevice && <DeviceDetail item={selectedDevice} />}
42+
</div>
43+
</div>
4244
);
4345
};
4446

0 commit comments

Comments
 (0)