update frontend
This commit is contained in:
228
frontend/src/components/ConfigForm.tsx
Normal file
228
frontend/src/components/ConfigForm.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import type { ConfigDeviceForm, ParameterSpec } from "../types";
|
||||
import { PROTOCOL_SPECS } from "../constants/protocols";
|
||||
|
||||
interface ConfigFormProps {
|
||||
deviceId?: number;
|
||||
configForm: ConfigDeviceForm;
|
||||
configFeedback: { type: "success" | "error"; message: string } | null;
|
||||
isConfigMutating: boolean;
|
||||
hasExistingConfig: boolean;
|
||||
currentProtocolSpec: (typeof PROTOCOL_SPECS)[number] | undefined;
|
||||
availableParameterSpecs: ParameterSpec[];
|
||||
onUpdateField: (field: "name" | "protocol" | "model", value: string) => void;
|
||||
onUpdateParameterRow: (
|
||||
index: number,
|
||||
field: "key" | "value",
|
||||
value: string
|
||||
) => void;
|
||||
onAddParameterRow: () => void;
|
||||
onRemoveParameterRow: (index: number) => void;
|
||||
onSave: (deviceId?: number) => void;
|
||||
onReset: (deviceId?: number) => void;
|
||||
onCancel: () => void;
|
||||
onDelete?: (deviceId: number) => void;
|
||||
}
|
||||
|
||||
const isEmpty = (value: string) =>
|
||||
value.trim() === "" || value === "null" || value === "0";
|
||||
|
||||
export const ConfigForm = ({
|
||||
deviceId,
|
||||
configForm,
|
||||
configFeedback,
|
||||
isConfigMutating,
|
||||
hasExistingConfig,
|
||||
currentProtocolSpec,
|
||||
availableParameterSpecs,
|
||||
onUpdateField,
|
||||
onUpdateParameterRow,
|
||||
onAddParameterRow,
|
||||
onRemoveParameterRow,
|
||||
onSave,
|
||||
onReset,
|
||||
onCancel,
|
||||
onDelete,
|
||||
}: ConfigFormProps) => {
|
||||
const idSuffix = deviceId ? `-${deviceId}` : "-new";
|
||||
|
||||
return (
|
||||
<div className="config-form">
|
||||
<div className="config-form-header">
|
||||
<h3>{deviceId ? `Configure Device #${deviceId}` : "Add New Device"}</h3>
|
||||
{hasExistingConfig && deviceId && onDelete && (
|
||||
<button
|
||||
type="button"
|
||||
className="danger"
|
||||
onClick={() => onDelete(deviceId)}
|
||||
disabled={isConfigMutating}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<label className="input-label" htmlFor={`config-name${idSuffix}`}>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
id={`config-name${idSuffix}`}
|
||||
value={configForm.name}
|
||||
onChange={(event) => onUpdateField("name", event.target.value)}
|
||||
placeholder="Living room lamp"
|
||||
/>
|
||||
|
||||
<label className="input-label" htmlFor={`config-protocol${idSuffix}`}>
|
||||
Protocol
|
||||
</label>
|
||||
<select
|
||||
id={`config-protocol${idSuffix}`}
|
||||
value={configForm.protocol}
|
||||
onChange={(event) => onUpdateField("protocol", event.target.value)}
|
||||
>
|
||||
<option value="">Select protocol...</option>
|
||||
{PROTOCOL_SPECS.map((spec) => (
|
||||
<option key={spec.name} value={spec.name}>
|
||||
{spec.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<label className="input-label" htmlFor={`config-model${idSuffix}`}>
|
||||
Model {currentProtocolSpec?.models ? "" : "(optional)"}
|
||||
</label>
|
||||
{currentProtocolSpec?.models ? (
|
||||
<select
|
||||
id={`config-model${idSuffix}`}
|
||||
value={configForm.model}
|
||||
onChange={(event) => onUpdateField("model", event.target.value)}
|
||||
>
|
||||
<option value="">Select model...</option>
|
||||
{currentProtocolSpec.models.map((model) => (
|
||||
<option key={model.name} value={model.name}>
|
||||
{model.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
id={`config-model${idSuffix}`}
|
||||
value={configForm.model}
|
||||
onChange={(event) => onUpdateField("model", event.target.value)}
|
||||
placeholder="Optional model name"
|
||||
disabled={!configForm.protocol}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="parameter-header">
|
||||
<h4>Parameters</h4>
|
||||
<button
|
||||
type="button"
|
||||
className="outline"
|
||||
onClick={onAddParameterRow}
|
||||
disabled={isConfigMutating}
|
||||
>
|
||||
Add parameter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="parameter-rows">
|
||||
{configForm.parameters.map((parameter, index) => {
|
||||
const paramSpec = availableParameterSpecs.find(
|
||||
(spec) => spec.name === parameter.key
|
||||
);
|
||||
console.log(
|
||||
"parameter:",
|
||||
parameter,
|
||||
"paramSpec:",
|
||||
paramSpec,
|
||||
!isEmpty(parameter.value)
|
||||
);
|
||||
if (!paramSpec && isEmpty(parameter.value)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="parameter-row" key={`parameter-${index}`}>
|
||||
{availableParameterSpecs.length > 0 ? (
|
||||
<select
|
||||
value={parameter.key}
|
||||
onChange={(event) =>
|
||||
onUpdateParameterRow(index, "key", event.target.value)
|
||||
}
|
||||
disabled={!configForm.protocol}
|
||||
>
|
||||
<option value="">Select parameter...</option>
|
||||
{availableParameterSpecs.map((spec) => (
|
||||
<option key={spec.name} value={spec.name}>
|
||||
{spec.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
value={parameter.key}
|
||||
placeholder="Parameter key"
|
||||
onChange={(event) =>
|
||||
onUpdateParameterRow(index, "key", event.target.value)
|
||||
}
|
||||
disabled={!configForm.protocol}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
value={parameter.value}
|
||||
placeholder={paramSpec ? paramSpec.description : "Value"}
|
||||
onChange={(event) =>
|
||||
onUpdateParameterRow(index, "value", event.target.value)
|
||||
}
|
||||
disabled={!parameter.key}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-btn subtle"
|
||||
onClick={() => onRemoveParameterRow(index)}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{configFeedback && (
|
||||
<div className={`config-feedback ${configFeedback.type}`}>
|
||||
{configFeedback.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="actions config-form-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="primary"
|
||||
onClick={() => onSave(deviceId)}
|
||||
disabled={isConfigMutating}
|
||||
>
|
||||
{hasExistingConfig
|
||||
? "Save changes"
|
||||
: deviceId
|
||||
? "Create config"
|
||||
: "Create device"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost"
|
||||
onClick={() => onReset(deviceId)}
|
||||
disabled={isConfigMutating}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ghost"
|
||||
onClick={onCancel}
|
||||
disabled={isConfigMutating}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user