feat: Phase 6, Device provisioning and deployment of updates on git-pull
This commit is contained in:
@@ -64,6 +64,12 @@ export default function DeviceInventoryDetail() {
|
||||
|
||||
const [nvsDownloading, setNvsDownloading] = useState(false);
|
||||
|
||||
const [assignEmail, setAssignEmail] = useState("");
|
||||
const [assignName, setAssignName] = useState("");
|
||||
const [assignSaving, setAssignSaving] = useState(false);
|
||||
const [assignError, setAssignError] = useState("");
|
||||
const [assignSuccess, setAssignSuccess] = useState(false);
|
||||
|
||||
const loadDevice = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
@@ -99,6 +105,28 @@ export default function DeviceInventoryDetail() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssign = async () => {
|
||||
setAssignError("");
|
||||
setAssignSaving(true);
|
||||
try {
|
||||
const updated = await api.request(`/manufacturing/devices/${sn}/assign`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
customer_email: assignEmail,
|
||||
customer_name: assignName || null,
|
||||
}),
|
||||
});
|
||||
setDevice(updated);
|
||||
setAssignSuccess(true);
|
||||
setAssignEmail("");
|
||||
setAssignName("");
|
||||
} catch (err) {
|
||||
setAssignError(err.message);
|
||||
} finally {
|
||||
setAssignSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const downloadNvs = async () => {
|
||||
setNvsDownloading(true);
|
||||
try {
|
||||
@@ -303,7 +331,7 @@ export default function DeviceInventoryDetail() {
|
||||
|
||||
{/* Actions card */}
|
||||
<div
|
||||
className="rounded-lg border p-5"
|
||||
className="rounded-lg border p-5 mb-4"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide mb-3" style={{ color: "var(--text-muted)" }}>
|
||||
@@ -326,6 +354,77 @@ export default function DeviceInventoryDetail() {
|
||||
NVS binary encodes device_uid, hw_type, hw_version. Flash at 0x9000.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Assign to Customer card */}
|
||||
{canEdit && ["provisioned", "flashed"].includes(device?.mfg_status) && (
|
||||
<div
|
||||
className="rounded-lg border p-5"
|
||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
||||
>
|
||||
<h2 className="text-sm font-semibold uppercase tracking-wide mb-3" style={{ color: "var(--text-muted)" }}>
|
||||
Assign to Customer
|
||||
</h2>
|
||||
|
||||
{assignSuccess ? (
|
||||
<div
|
||||
className="text-sm rounded-md p-3 border"
|
||||
style={{ backgroundColor: "#0a2e2a", borderColor: "#4dd6c8", color: "#4dd6c8" }}
|
||||
>
|
||||
Device assigned and invitation email sent to <strong>{device?.owner}</strong>.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{assignError && (
|
||||
<div
|
||||
className="text-xs rounded p-2 border"
|
||||
style={{
|
||||
backgroundColor: "var(--danger-bg)",
|
||||
borderColor: "var(--danger)",
|
||||
color: "var(--danger-text)",
|
||||
}}
|
||||
>
|
||||
{assignError}
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Customer email address"
|
||||
value={assignEmail}
|
||||
onChange={(e) => setAssignEmail(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded-md text-sm border"
|
||||
style={{
|
||||
backgroundColor: "var(--bg-input)",
|
||||
borderColor: "var(--border-input)",
|
||||
color: "var(--text-primary)",
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Customer name (optional)"
|
||||
value={assignName}
|
||||
onChange={(e) => setAssignName(e.target.value)}
|
||||
className="w-full px-3 py-2 rounded-md text-sm border"
|
||||
style={{
|
||||
backgroundColor: "var(--bg-input)",
|
||||
borderColor: "var(--border-input)",
|
||||
color: "var(--text-primary)",
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={handleAssign}
|
||||
disabled={assignSaving || !assignEmail.trim()}
|
||||
className="px-4 py-1.5 text-sm rounded-md hover:opacity-90 transition-opacity cursor-pointer disabled:opacity-50"
|
||||
style={{ backgroundColor: "var(--btn-primary)", color: "var(--text-white)" }}
|
||||
>
|
||||
{assignSaving ? "Sending…" : "Assign & Send Invite"}
|
||||
</button>
|
||||
<p className="text-xs" style={{ color: "var(--text-muted)" }}>
|
||||
Sets device status to <em>sold</em> and emails the customer their serial number.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user