Skip to content

Commit d304d4a

Browse files
authored
refactor: centralize feature API calls (#297)
* refactor: centralize feature API calls - Move cohorts, dashboard, search and trainee-profile HTTP calls into feature api/api.ts modules - Update React Query hooks to consume typed API helpers - Extract shared search response and trainee save request types Refs: #287 * Improved error handling * Removed try/catch to preserve 401 redirect functionality * improved parameter handling * refactor: streamline API response handling in trainee info functions * fixed params for axios
1 parent 73591cf commit d304d4a

File tree

16 files changed

+206
-116
lines changed

16 files changed

+206
-116
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import axios from 'axios';
2+
import { Cohort } from '../Cohorts';
3+
4+
export const getCohorts = async () => {
5+
const { data } = await axios.get<Cohort[]>('/api/cohorts');
6+
return data;
7+
};
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { Cohort } from '../Cohorts';
2-
import axios from 'axios';
31
import { useQuery } from '@tanstack/react-query';
2+
import { getCohorts } from '../api/api';
43

54
/**
65
* A React Query hook that fetches cohort data form api for specific dates.
76
*/
87
export const useCohortsData = () => {
98
return useQuery({
109
queryKey: ['CohortsInfo'],
11-
queryFn: async () => {
12-
const { data } = await axios.get<Cohort[]>(`/api/cohorts`);
13-
return data;
14-
},
10+
queryFn: getCohorts,
1511
refetchOnWindowFocus: false, // Prevent refetching on window focus
1612
});
1713
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import axios from 'axios';
2+
import { DashboardData } from '../Dashboard';
3+
4+
export const getDashboardData = async (startDate?: string, endDate?: string): Promise<DashboardData> => {
5+
const params = {
6+
...(startDate && { startDate }),
7+
...(endDate && { endDate }),
8+
};
9+
10+
const response = await axios.get<DashboardData>('/api/dashboard', { params });
11+
12+
return response.data;
13+
};
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
import { DashboardData } from '../Dashboard';
2-
import axios from 'axios';
31
import { useQuery } from '@tanstack/react-query';
2+
import { getDashboardData } from '../api/api';
43

54
/**
6-
* A React Query hook that fetches dashboard data form api for specific dates.
5+
* A React Query hook that fetches dashboard data from api for specific dates.
76
*
87
* @param {string | undefined} startDate
98
* @param {string | undefined} endDate
109
*/
1110
export const useDashboardData = (startDate: string | undefined, endDate: string | undefined) => {
1211
return useQuery({
1312
queryKey: ['DashboardInfo'],
14-
queryFn: async () => {
15-
const response = await axios.get<DashboardData>(`/api/dashboard?startDate=${startDate}&endDate=${endDate}`);
16-
return response.data;
17-
},
13+
queryFn: () => getDashboardData(startDate, endDate),
1814
refetchOnWindowFocus: false, // Prevent refetching on window focus
1915
});
2016
};

client/src/features/search/Search.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@ export interface SearchResult {
55
profilePath: string;
66
cohort: number | null;
77
}
8+
9+
export interface SearchResultResponse {
10+
hits: {
11+
data: SearchResult[];
12+
};
13+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import axios from 'axios';
2+
import { SearchResult, SearchResultResponse } from '../Search';
3+
4+
export const getSearchResults = async (query: string): Promise<SearchResult[]> => {
5+
const response = await axios.get<SearchResultResponse>('/api/search', {
6+
params: { q: query, limit: 20 },
7+
});
8+
return response.data.hits.data;
9+
};
Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { SearchResult } from '../Search';
2-
import axios from 'axios';
31
import { useQuery } from '@tanstack/react-query';
2+
import { getSearchResults } from '../api/api';
43

54
export const searchKeys = {
65
all: ['search'] as const, // for broad invalidation
@@ -11,23 +10,9 @@ export const searchKeys = {
1110
export const useSearch = (query: string) => {
1211
return useQuery({
1312
queryKey: searchKeys.byQuery(query),
14-
queryFn: async () => {
15-
const response = await axios.get(`/api/search?q=${query}&limit=20`);
16-
const trainees: SearchResult[] = response.data.hits.data.map((trainee: SearchResult) => trainee);
17-
return trainees;
18-
},
13+
queryFn: () => getSearchResults(query),
1914
enabled: query.length > 1, // Query runs only if search string has more than 1 character
2015
refetchOnMount: false,
2116
refetchOnWindowFocus: false,
2217
});
2318
};
24-
25-
interface SearchResultResponse {
26-
hits: {
27-
data: SearchResult[];
28-
};
29-
}
30-
export const getSearchResults = async (query: string): Promise<SearchResult[]> => {
31-
const response = await axios.get<SearchResultResponse>(`/api/search?q=${query}&limit=20`);
32-
return response.data.hits.data;
33-
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import axios from 'axios';
2+
import { Strike } from '../../../../../data/types/Trainee';
3+
4+
export const getStrikes = async (traineeId: string) => {
5+
const { data } = await axios.get<Strike[]>(`/api/trainees/${traineeId}/strikes`);
6+
return data;
7+
};
8+
9+
export const addStrike = async (traineeId: string, strike: Strike) => {
10+
try {
11+
await axios.post(`/api/trainees/${traineeId}/strikes`, strike);
12+
} catch (error) {
13+
if (axios.isAxiosError(error)) {
14+
throw new Error(error.response?.data?.error || 'Failed to add strike');
15+
}
16+
17+
throw new Error(error instanceof Error ? error.message : 'An unexpected error occurred');
18+
}
19+
};
20+
21+
export const deleteStrike = async (traineeId: string, strikeId: string) => {
22+
await axios.delete(`/api/trainees/${traineeId}/strikes/${strikeId}`);
23+
};
24+
25+
export const editStrike = async (traineeId: string, strike: Strike) => {
26+
try {
27+
await axios.put(`/api/trainees/${traineeId}/strikes/${strike.id}`, strike);
28+
} catch (error) {
29+
if (axios.isAxiosError(error)) {
30+
throw new Error(error.response?.data?.error || 'Failed to edit strike');
31+
}
32+
33+
throw new Error(error instanceof Error ? error.message : 'An unexpected error occurred');
34+
}
35+
};

client/src/features/trainee-profile/education/strikes/data/strike-queries.ts

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useMutation, useQuery } from '@tanstack/react-query';
2-
32
import { Strike } from '../../../../../data/types/Trainee';
4-
import axios from 'axios';
3+
import { getStrikes, addStrike, deleteStrike, editStrike } from '../api/api';
54

65
/**
76
* Hook to add a strike to a trainee.
@@ -10,11 +9,7 @@ import axios from 'axios';
109
*/
1110
export const useAddStrike = (traineeId: string) => {
1211
return useMutation({
13-
mutationFn: async (strike: Strike) => {
14-
return axios.post(`/api/trainees/${traineeId}/strikes`, strike).catch((error) => {
15-
throw new Error(error.response?.data?.error || 'Failed to add strike');
16-
});
17-
},
12+
mutationFn: (strike: Strike) => addStrike(traineeId, strike),
1813
});
1914
};
2015

@@ -27,8 +22,8 @@ export const useGetStrikes = (traineeId: string) => {
2722
return useQuery({
2823
queryKey: ['strikes', traineeId],
2924
queryFn: async () => {
30-
const { data } = await axios.get<Strike[]>(`/api/trainees/${traineeId}/strikes`);
31-
return orderStrikesByDateDesc(data as Strike[]);
25+
const data = await getStrikes(traineeId);
26+
return orderStrikesByDateDesc(data);
3227
},
3328
enabled: !!traineeId,
3429
refetchOnWindowFocus: false,
@@ -43,9 +38,7 @@ export const useGetStrikes = (traineeId: string) => {
4338

4439
export const useDeleteStrike = (traineeId: string) => {
4540
return useMutation({
46-
mutationFn: async (strikeId: string) => {
47-
return axios.delete(`/api/trainees/${traineeId}/strikes/${strikeId}`);
48-
},
41+
mutationFn: (strikeId: string) => deleteStrike(traineeId, strikeId),
4942
});
5043
};
5144

@@ -55,11 +48,7 @@ export const useDeleteStrike = (traineeId: string) => {
5548
*/
5649
export const useEditStrike = (traineeId: string) => {
5750
return useMutation({
58-
mutationFn: async (strike: Strike) => {
59-
return axios.put(`/api/trainees/${traineeId}/strikes/${strike.id}`, strike).catch((error) => {
60-
throw new Error(error.response?.data?.error || 'Failed to edit strike');
61-
});
62-
},
51+
mutationFn: (strike: Strike) => editStrike(traineeId, strike),
6352
});
6453
};
6554

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import axios from 'axios';
2+
import { Test } from '../../../../../data/types/Trainee';
3+
4+
export const getTests = async (traineeId: string) => {
5+
const { data } = await axios.get<Test[]>(`/api/trainees/${traineeId}/tests`);
6+
return data;
7+
};
8+
9+
export const addTest = async (traineeId: string, test: Test) => {
10+
try {
11+
await axios.post(`/api/trainees/${traineeId}/tests`, test);
12+
} catch (error) {
13+
if (axios.isAxiosError(error)) {
14+
throw new Error(error.response?.data?.error || 'Failed to add test');
15+
}
16+
17+
throw new Error(error instanceof Error ? error.message : 'An unexpected error occurred');
18+
}
19+
};
20+
21+
export const deleteTest = async (traineeId: string, testId: string) => {
22+
await axios.delete(`/api/trainees/${traineeId}/tests/${testId}`);
23+
};
24+
25+
export const editTest = async (traineeId: string, test: Test) => {
26+
try {
27+
await axios.put(`/api/trainees/${traineeId}/tests/${test.id}`, test);
28+
} catch (error) {
29+
if (axios.isAxiosError(error)) {
30+
throw new Error(error.response?.data?.error || 'Failed to edit test');
31+
}
32+
33+
throw new Error(error instanceof Error ? error.message : 'An unexpected error occurred');
34+
}
35+
};

0 commit comments

Comments
 (0)