Skip to content

Commit dafdbc8

Browse files
committed
Add option to delete maintenance records
1 parent 23be28c commit dafdbc8

2 files changed

Lines changed: 139 additions & 26 deletions

File tree

backend/src/main/java/com/apexgrid/transformertracker/web/InspectionController.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,19 @@ public ResponseEntity<?> upsertMaintenanceRecord(@PathVariable String id,
123123
return ResponseEntity.ok(MaintenanceRecordResponse.fromEntity(saved));
124124
}
125125

126+
@DeleteMapping("/{id}/maintenance-record")
127+
public ResponseEntity<?> deleteMaintenanceRecord(@PathVariable String id) {
128+
if (!repo.existsById(id)) {
129+
return ResponseEntity.status(404).body(Map.of("error", "Inspection not found"));
130+
}
131+
var recordOpt = maintenanceRecordRepo.findByInspectionId(id);
132+
if (recordOpt.isEmpty()) {
133+
return ResponseEntity.status(404).body(Map.of("error", "Maintenance record not found"));
134+
}
135+
maintenanceRecordRepo.delete(recordOpt.get());
136+
return ResponseEntity.noContent().build();
137+
}
138+
126139
@GetMapping("/{id}/export")
127140
public ResponseEntity<?> exportInspection(@PathVariable String id) {
128141
return repo.findById(id).map(inspection -> {

frontend/components/InspectionDetailsPanel.tsx

Lines changed: 126 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,9 @@ const InspectionDetailsPanel = ({
464464
const [maintenanceModalMode, setMaintenanceModalMode] = useState<"form" | "view">("form");
465465
const [showMaintenanceModal, setShowMaintenanceModal] = useState(false);
466466
const [isSavingMaintenance, setIsSavingMaintenance] = useState(false);
467+
const [isDeletingMaintenance, setIsDeletingMaintenance] = useState(false);
468+
const [showDeleteMaintenanceConfirm, setShowDeleteMaintenanceConfirm] = useState(false);
469+
const [deleteMaintenanceError, setDeleteMaintenanceError] = useState<string | null>(null);
467470
const [isReportGenerating, setIsReportGenerating] = useState(false);
468471
const [userRole, setUserRole] = useState<string | null>(null);
469472

@@ -1183,10 +1186,20 @@ const InspectionDetailsPanel = ({
11831186
};
11841187

11851188
const closeMaintenanceModal = () => {
1186-
if (isSavingMaintenance) return;
1189+
if (isSavingMaintenance || isDeletingMaintenance) return;
11871190
setShowMaintenanceModal(false);
11881191
};
11891192

1193+
const openMaintenanceDeleteConfirm = () => {
1194+
setDeleteMaintenanceError(null);
1195+
setShowDeleteMaintenanceConfirm(true);
1196+
};
1197+
1198+
const closeMaintenanceDeleteConfirm = () => {
1199+
if (isDeletingMaintenance) return;
1200+
setShowDeleteMaintenanceConfirm(false);
1201+
};
1202+
11901203
const handleMaintenanceInputChange = (
11911204
field: keyof MaintenanceFormState,
11921205
value: string
@@ -1195,7 +1208,7 @@ const InspectionDetailsPanel = ({
11951208
};
11961209

11971210
const handleMaintenanceSave = async () => {
1198-
if (!inspection.id) return;
1211+
if (!inspection.id || isSavingMaintenance || isDeletingMaintenance) return;
11991212
const trimmedTimestamp = maintenanceForm.timestamp.trim();
12001213
if (!trimmedTimestamp) {
12011214
setMaintenanceFormError("Timestamp is required.");
@@ -1247,6 +1260,46 @@ const InspectionDetailsPanel = ({
12471260
}
12481261
};
12491262

1263+
const handleMaintenanceDelete = async () => {
1264+
if (!inspection.id || !maintenanceRecord || isDeletingMaintenance || isSavingMaintenance) {
1265+
return;
1266+
}
1267+
setIsDeletingMaintenance(true);
1268+
setMaintenanceFormError(null);
1269+
setDeleteMaintenanceError(null);
1270+
try {
1271+
const response = await fetch(
1272+
apiUrl(`/api/inspections/${inspection.id}/maintenance-record`),
1273+
{
1274+
method: "DELETE",
1275+
headers: {
1276+
...authHeaders(),
1277+
},
1278+
}
1279+
);
1280+
if (!response.ok) {
1281+
const text = await response.text().catch(() => "");
1282+
throw new Error(
1283+
text || `Failed to delete maintenance record (${response.status})`
1284+
);
1285+
}
1286+
setMaintenanceRecord(null);
1287+
setMaintenanceForm(buildMaintenanceFormState());
1288+
setMaintenanceError(null);
1289+
setShowDeleteMaintenanceConfirm(false);
1290+
setShowMaintenanceModal(false);
1291+
} catch (error) {
1292+
const message =
1293+
error instanceof Error
1294+
? error.message
1295+
: "Unable to delete maintenance record.";
1296+
setMaintenanceFormError(message);
1297+
setDeleteMaintenanceError(message);
1298+
} finally {
1299+
setIsDeletingMaintenance(false);
1300+
}
1301+
};
1302+
12501303
const handleResetModelClick = () => {
12511304
setResetModelMessage(null);
12521305
setResetModelError(null);
@@ -3014,12 +3067,12 @@ const InspectionDetailsPanel = ({
30143067
<span className="block text-xs uppercase tracking-wide mb-1">Timestamp *</span>
30153068
<input
30163069
type="text"
3017-
className="w-full border border-gray-300 dark:border-gray-600 rounded px-3 py-2 text-sm bg-white dark:bg-[#111]"
3070+
className="w-full border border-gray-300 dark:border-gray-600 rounded px-3 py-2 text-sm bg-gray-100 dark:bg-[#1a1a1a] cursor-not-allowed"
30183071
placeholder="e.g. 2025-11-24T10:00Z"
30193072
value={maintenanceForm.timestamp}
3020-
onChange={(event) =>
3021-
handleMaintenanceInputChange("timestamp", event.target.value)
3022-
}
3073+
readOnly
3074+
aria-readonly="true"
3075+
title="Timestamp is generated automatically"
30233076
/>
30243077
</label>
30253078
<label className="text-sm text-gray-700 dark:text-gray-200">
@@ -3103,26 +3156,38 @@ const InspectionDetailsPanel = ({
31033156
}
31043157
/>
31053158
</label>
3106-
<div className="flex justify-end gap-2">
3107-
<button
3108-
type="button"
3109-
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded text-gray-900 dark:text-white"
3110-
onClick={closeMaintenanceModal}
3111-
disabled={isSavingMaintenance}
3112-
>
3113-
Cancel
3114-
</button>
3115-
<button
3116-
type="submit"
3117-
className="px-4 py-1.5 text-sm rounded bg-green-600 text-white disabled:opacity-60"
3118-
disabled={isSavingMaintenance}
3119-
>
3120-
{isSavingMaintenance
3121-
? "Saving…"
3122-
: maintenanceRecord
3123-
? "Update record"
3124-
: "Save record"}
3125-
</button>
3159+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
3160+
{maintenanceRecord && (
3161+
<button
3162+
type="button"
3163+
className="w-full sm:w-auto px-4 py-1.5 text-sm rounded border border-red-200 bg-red-50 text-red-700 dark:border-red-700/50 dark:bg-red-900/20 dark:text-red-300 disabled:opacity-60"
3164+
onClick={openMaintenanceDeleteConfirm}
3165+
disabled={isSavingMaintenance || isDeletingMaintenance}
3166+
>
3167+
{isDeletingMaintenance ? "Deleting…" : "Delete record"}
3168+
</button>
3169+
)}
3170+
<div className="flex justify-end gap-2">
3171+
<button
3172+
type="button"
3173+
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded text-gray-900 dark:text-white"
3174+
onClick={closeMaintenanceModal}
3175+
disabled={isSavingMaintenance || isDeletingMaintenance}
3176+
>
3177+
Cancel
3178+
</button>
3179+
<button
3180+
type="submit"
3181+
className="px-4 py-1.5 text-sm rounded bg-green-600 text-white disabled:opacity-60"
3182+
disabled={isSavingMaintenance || isDeletingMaintenance}
3183+
>
3184+
{isSavingMaintenance
3185+
? "Saving…"
3186+
: maintenanceRecord
3187+
? "Update record"
3188+
: "Save record"}
3189+
</button>
3190+
</div>
31263191
</div>
31273192
</form>
31283193
<MaintenanceAnnotationPreview
@@ -3139,6 +3204,41 @@ const InspectionDetailsPanel = ({
31393204
</div>
31403205
</div>
31413206
)}
3207+
{showDeleteMaintenanceConfirm && (
3208+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4">
3209+
<div className="details-panel w-full max-w-md rounded-lg bg-white dark:bg-[#111] p-5 shadow-2xl">
3210+
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
3211+
Delete maintenance record?
3212+
</h3>
3213+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-4">
3214+
This record will be removed permanently and cannot be recovered. Are you sure you want to continue?
3215+
</p>
3216+
{deleteMaintenanceError && (
3217+
<div className="mb-3 text-sm text-red-600" role="alert">
3218+
{deleteMaintenanceError}
3219+
</div>
3220+
)}
3221+
<div className="flex justify-end gap-2">
3222+
<button
3223+
type="button"
3224+
className="px-3 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded text-gray-900 dark:text-white"
3225+
onClick={closeMaintenanceDeleteConfirm}
3226+
disabled={isDeletingMaintenance}
3227+
>
3228+
Cancel
3229+
</button>
3230+
<button
3231+
type="button"
3232+
className="px-4 py-1.5 text-sm rounded bg-red-600 text-white disabled:opacity-60"
3233+
onClick={() => void handleMaintenanceDelete()}
3234+
disabled={isDeletingMaintenance}
3235+
>
3236+
{isDeletingMaintenance ? "Deleting…" : "Delete"}
3237+
</button>
3238+
</div>
3239+
</div>
3240+
</div>
3241+
)}
31423242
{/* Fault selection modal */}
31433243
{showFaultModal && pendingRect && (
31443244
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">

0 commit comments

Comments
 (0)