Skip to content

Commit 7008b73

Browse files
committed
chore: Hook up backend API calls for adding repairs
1 parent eb6e256 commit 7008b73

8 files changed

Lines changed: 238 additions & 13 deletions

File tree

admin/frontend/src/pages/rec-resource-page/components/RecResourceAssetsSection/AddRepairModal.tsx

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
55
import clsx from 'clsx';
66
import Select from 'react-select';
77
import { CustomButton } from '@/components';
8+
import { useBulkInsertAssetRepairs } from '@/services/hooks/recreation-resource-admin';
9+
import { buildBulkRepairPayload } from './buildBulkRepairPayload';
810
import {
911
createRepairGroupFormState,
1012
isRepairGroupValid,
@@ -32,6 +34,7 @@ export interface AddRepairFormState {
3234

3335
interface AddRepairModalProps {
3436
show: boolean;
37+
recResourceId: string;
3538
repairCodes: RepairCode[];
3639
assetCodes: AssetCode[];
3740
assets: Asset[];
@@ -41,6 +44,7 @@ interface AddRepairModalProps {
4144

4245
export function AddRepairModal({
4346
show,
47+
recResourceId,
4448
repairCodes,
4549
assetCodes,
4650
assets,
@@ -49,6 +53,7 @@ export function AddRepairModal({
4953
}: AddRepairModalProps) {
5054
const { scrollRef, contentRef, canScrollUp, canScrollDown } =
5155
useModalScrollFade();
56+
const bulkInsertAssetRepairs = useBulkInsertAssetRepairs();
5257
const [selectedRepairType, setSelectedRepairType] = useState('');
5358
const [completionDate, setCompletionDate] = useState('');
5459
const [repairGroups, setRepairGroups] = useState<RepairGroupFormState[]>([
@@ -116,10 +121,27 @@ export function AddRepairModal({
116121
isRepairGroupValid(group, assets, assetCodes),
117122
);
118123

119-
if (isValid) {
120-
onCreate();
121-
resetForm();
124+
if (!isValid) {
125+
return;
122126
}
127+
128+
const dto = buildBulkRepairPayload(
129+
selectedRepairType,
130+
completionDate,
131+
repairGroups,
132+
assets,
133+
assetCodes,
134+
);
135+
136+
bulkInsertAssetRepairs.mutate(
137+
{ recResourceId, dto },
138+
{
139+
onSuccess: () => {
140+
resetForm();
141+
onCreate();
142+
},
143+
},
144+
);
123145
};
124146

125147
return (
@@ -233,15 +255,21 @@ export function AddRepairModal({
233255
</div>
234256
</Modal.Body>
235257
<Modal.Footer className="add-repair-modal__footer">
236-
<CustomButton variant="outline-primary" onClick={handleCancel}>
258+
<CustomButton
259+
variant="outline-primary"
260+
onClick={handleCancel}
261+
disabled={bulkInsertAssetRepairs.isPending}
262+
>
237263
Cancel
238264
</CustomButton>
239265
<CustomButton
240266
variant="primary"
241267
onClick={handleCreateClick}
242-
disabled={!hasSelectedAsset}
268+
disabled={!hasSelectedAsset || bulkInsertAssetRepairs.isPending}
243269
>
244-
Create repairs
270+
{bulkInsertAssetRepairs.isPending
271+
? 'Creating repairs...'
272+
: 'Create repairs'}
245273
</CustomButton>
246274
</Modal.Footer>
247275
</Modal>

admin/frontend/src/pages/rec-resource-page/components/RecResourceAssetsSection/RecResourceAssetsSection.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ export function RecResourceAssetsSection() {
212212

213213
<AddRepairModal
214214
show={isAddRepairModalOpen}
215+
recResourceId={recResourceId}
215216
repairCodes={repairCodes}
216217
assetCodes={assetCodes ?? []}
217218
assets={assets ?? []}

admin/frontend/src/pages/rec-resource-page/components/RecResourceAssetsSection/RepairAssetEntry.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ export function createRepairGroupFormState(id: number): RepairGroupFormState {
4444
};
4545
}
4646

47-
// Shared with AddRepairModal so the "Create repairs" submit check validates
48-
// against exactly the same matching/selection logic this component renders.
49-
function getRepairGroupContext(
47+
// Shared with AddRepairModal so the "Create repairs" submit check and the
48+
// submit payload builder both use exactly the same matching/selection logic
49+
// this component renders.
50+
export function getRepairGroupContext(
5051
entry: RepairGroupFormState,
5152
assets: Asset[],
5253
assetCodes: AssetCode[],
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type {
2+
RecreationAssetBulkRepairDto,
3+
RepairChange,
4+
} from '@/services/recreation-resource-admin';
5+
import { getRepairGroupContext } from './RepairAssetEntry';
6+
import type { RepairGroupFormState } from './RepairAssetEntry';
7+
import type { Asset, AssetCode } from './types';
8+
9+
/**
10+
* Builds the bulk-repair API payload from the modal's form state. Assumes
11+
* every group has already passed isRepairGroupValid. Non-trail groups
12+
* collapse into one RepairChange covering every selected asset; trail groups
13+
* split into one RepairChange per selected asset so each can carry its own
14+
* start/end station.
15+
*/
16+
export function buildBulkRepairPayload(
17+
recreationRemedRepairCode: string,
18+
completionDate: string,
19+
repairGroups: RepairGroupFormState[],
20+
assets: Asset[],
21+
assetCodes: AssetCode[],
22+
): RecreationAssetBulkRepairDto {
23+
const changes: RepairChange[] = repairGroups
24+
.filter((group) => group.assetTypeCode)
25+
.flatMap((group) => {
26+
const { selectedAssets, isTrailAssetType } = getRepairGroupContext(
27+
group,
28+
assets,
29+
assetCodes,
30+
);
31+
32+
if (isTrailAssetType) {
33+
return selectedAssets.map((asset) => ({
34+
estimated_repair_cost: group.estimatedCost ?? 0,
35+
actual_repair_cost: group.actualCost,
36+
station_start: group.trailStations[asset.asset_id]?.startStation,
37+
station_end: group.trailStations[asset.asset_id]?.endStation,
38+
asset_ids: [asset.asset_id],
39+
}));
40+
}
41+
42+
return [
43+
{
44+
estimated_repair_cost: group.estimatedCost ?? 0,
45+
actual_repair_cost: group.actualCost,
46+
asset_ids: selectedAssets.map((asset) => asset.asset_id),
47+
},
48+
];
49+
});
50+
51+
return {
52+
recreation_remed_repair_code: recreationRemedRepairCode,
53+
completed_date: completionDate || undefined,
54+
changes,
55+
};
56+
}

admin/frontend/src/services/hooks/recreation-resource-admin/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export * from './useCreateTrail';
2828
export * from './useUpdateTrail';
2929
export * from './useDeleteTrail';
3030
export * from './useAssetsApiClient';
31+
export * from './useBulkInsertAssetRepairs';
3132
export * from './useGetAssetsByRecResourceId';
3233
export * from './useGetAssetCodes';
3334
export * from './useGetRepairCodes';
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import {
2+
RecreationAssetBulkRepairDto,
3+
ResponseError,
4+
} from '@/services/recreation-resource-admin';
5+
import { useAssetsApiClient } from '@/services/hooks/recreation-resource-admin/useAssetsApiClient';
6+
import {
7+
addErrorNotification,
8+
addSuccessNotification,
9+
} from '@/store/notificationStore';
10+
import { useMutation, useQueryClient } from '@tanstack/react-query';
11+
import { createRetryHandler } from './helpers';
12+
import { RECREATION_RESOURCE_QUERY_KEYS } from './queryKeys';
13+
14+
export interface BulkInsertAssetRepairsRequest {
15+
recResourceId: string;
16+
dto: RecreationAssetBulkRepairDto;
17+
}
18+
19+
export function useBulkInsertAssetRepairs() {
20+
const assetsApiClient = useAssetsApiClient();
21+
const queryClient = useQueryClient();
22+
23+
return useMutation<void, ResponseError, BulkInsertAssetRepairsRequest>({
24+
mutationFn: async ({ dto }) => {
25+
await assetsApiClient.bulkInsertAssetRepairs({
26+
recreationAssetBulkRepairDto: dto,
27+
});
28+
},
29+
retry: createRetryHandler({
30+
onFail: () =>
31+
addErrorNotification(
32+
'Failed to add repairs after multiple attempts. Please try again later.',
33+
'bulkInsertAssetRepairs-error',
34+
),
35+
}),
36+
onSuccess: (_data, variables) => {
37+
addSuccessNotification(
38+
'Repairs added successfully',
39+
'bulkInsertAssetRepairs-success',
40+
);
41+
queryClient.invalidateQueries({
42+
queryKey: RECREATION_RESOURCE_QUERY_KEYS.assets(
43+
variables.recResourceId,
44+
),
45+
});
46+
},
47+
onError: (error) => {
48+
const statusCode = error.response?.status;
49+
if (statusCode === 404) {
50+
addErrorNotification(
51+
'One or more selected assets could not be found',
52+
'bulkInsertAssetRepairs-notfound',
53+
);
54+
} else if (statusCode === 400) {
55+
addErrorNotification(
56+
'Invalid repair details. Please check the form and try again',
57+
'bulkInsertAssetRepairs-badrequest',
58+
);
59+
}
60+
},
61+
});
62+
}

0 commit comments

Comments
 (0)