implement TeleEMS platform architecture with centralized API client and master data management system

This commit is contained in:
2026-05-04 15:27:35 +05:30
parent e165269e92
commit 8dc773d205
61 changed files with 22829 additions and 0 deletions

134
src/api/apiClient.ts Normal file
View File

@@ -0,0 +1,134 @@
import { logout } from '../utils/auth';
const BASE_URL = 'https://teleems-api-gateway.onrender.com';
interface RequestOptions extends RequestInit {
token?: string;
}
/**
* Centralized API client that handles automatic token injection
* and global error handling (like 401 Unauthorized)
*/
export const apiClient = {
request: async (endpoint: string, options: RequestOptions = {}) => {
const { token, headers, ...rest } = options;
// Use provided token or get from localStorage
const authToken = token || localStorage.getItem('teleems_token');
const defaultHeaders: Record<string, string> = {
'Content-Type': 'application/json',
};
if (authToken) {
defaultHeaders['Authorization'] = `Bearer ${authToken}`;
}
// --- MOCK BYPASS FOR DEMO SESSIONS ---
if (authToken && (
authToken.startsWith('mock-') ||
authToken.startsWith('dev-token-') ||
authToken === 'dev-super-token-2026'
)) {
return new Promise((resolve) => {
setTimeout(() => {
if (endpoint.includes('/v1/incidents')) {
resolve({
status: 200,
data: [
{
id: 'INC-MOCK-001',
category: 'MEDICAL',
severity: 'CRITICAL',
status: 'PENDING',
address: 'Sector 7G, Tactical Hub',
notes: 'High-priority mock incident for system validation.',
createdAt: new Date().toISOString(),
gps_lat: 13.0827,
gps_lon: 80.2707,
patients: [{ name: 'Tactical Test', age: 34, gender: 'Male', symptoms: ['None'], triage_code: 'RED' }]
}
]
});
} else if (endpoint.includes('/v1/auth/users')) {
resolve({
status: 200,
data: [
{ id: 'u1', username: 'admin', roles: ['CURESELECT_ADMIN'], status: 'ACTIVE', email: 'admin@teleems.com' },
{ id: 'u2', username: 'fleet_op', roles: ['FLEET_OPERATOR'], status: 'ACTIVE', email: 'fleet@teleems.com' }
]
});
} else if (endpoint.includes('/v1/auth/audit-logs')) {
resolve({
status: 200,
data: {
logs: [
{ id: 'l1', action: 'LOGIN_SUCCESS', createdAt: new Date().toISOString(), ipAddress: '127.0.0.1', user: { username: 'admin' } },
{ id: 'l2', action: 'INCIDENT_VIEW', createdAt: new Date().toISOString(), ipAddress: '127.0.0.1', user: { username: 'admin' } }
],
total: 2
}
});
} else {
resolve({ status: 200, data: [] });
}
}, 500);
});
}
const url = endpoint.startsWith('http') ? endpoint : `${BASE_URL}${endpoint}`;
try {
const response = await fetch(url, {
headers: { ...defaultHeaders, ...headers },
...rest,
});
// Handle session expiration
if (response.status === 401 || response.status === 403) {
console.warn('Unauthorized request detected. Triggering auto-logout...');
logout();
return null; // Return null as the app will redirect
}
const data = await response.json();
if (!response.ok) {
return { ...data, status: response.status };
}
return data;
} catch (error) {
console.error('API Request Error:', error);
throw error;
}
},
get: (endpoint: string, options: RequestOptions = {}) =>
apiClient.request(endpoint, { ...options, method: 'GET' }),
post: (endpoint: string, body: any, options: RequestOptions = {}) =>
apiClient.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(body)
}),
put: (endpoint: string, body: any, options: RequestOptions = {}) =>
apiClient.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(body)
}),
patch: (endpoint: string, body: any, options: RequestOptions = {}) =>
apiClient.request(endpoint, {
...options,
method: 'PATCH',
body: JSON.stringify(body)
}),
delete: (endpoint: string, options: RequestOptions = {}) =>
apiClient.request(endpoint, { ...options, method: 'DELETE' }),
};

55
src/api/auth.ts Normal file
View File

@@ -0,0 +1,55 @@
import { apiClient } from './apiClient';
import type { AuthUser, LoginResponse, MfaSetupResponse, MfaVerifyResponse } from './types';
export const authApi = {
login: async (username: string, password: string): Promise<LoginResponse> => {
return apiClient.post('/v1/auth/login', { username, password });
},
verifyMfa: async (mfaSessionToken: string, totpCode: string): Promise<LoginResponse> => {
return apiClient.post('/v1/auth/mfa/verify', {
mfa_session_token: mfaSessionToken,
totp_code: totpCode
});
},
setupMfa: async (token: string): Promise<MfaSetupResponse> => {
return apiClient.post('/v1/auth/mfa/totp/setup', {}, { token });
},
verifyTotpSetup: async (token: string, totpCode: string): Promise<MfaVerifyResponse> => {
return apiClient.post('/v1/auth/mfa/totp/verify', { totp_code: totpCode }, { token });
},
getAuditLogs: async (token: string, limit = 20, offset = 0) => {
return apiClient.get(`/v1/auth/audit-logs?limit=${limit}&offset=${offset}`, { token });
},
registerUser: async (userData: any, token: string) => {
return apiClient.post('/v1/auth/users', userData, { token });
},
getUsers: async (token: string) => {
return apiClient.get('/v1/auth/users', { token });
},
updateUser: async (userId: string, userData: any, token: string) => {
return apiClient.put(`/v1/auth/users/${userId}`, userData, { token });
},
disableMfa: async (password: string, token: string) => {
return apiClient.post('/v1/auth/mfa/disable', { password }, { token });
},
getDepartments: async (token: string) => {
return apiClient.get('/v1/hospital/departments', { token });
},
createDepartment: async (deptData: any, token: string) => {
return apiClient.post('/v1/hospital/departments', deptData, { token });
},
getRoles: async (token: string) => {
return apiClient.get('/v1/auth/roles', { token });
}
};

47
src/api/fleet.ts Normal file
View File

@@ -0,0 +1,47 @@
import { apiClient } from './apiClient';
export const fleetApi = {
createStation: async (stationData: any, token: string) => {
return apiClient.post('/v1/fleet/stations', stationData, { token });
},
getStations: async (token: string, organisationId?: string) => {
const url = organisationId
? `/v1/fleet/stations?organisationId=${organisationId}`
: `/v1/fleet/stations`;
return apiClient.get(url, { token });
},
createVehicle: async (vehicleData: any, token: string) => {
return apiClient.post('/v1/fleet/vehicles', vehicleData, { token });
},
getVehicles: async (token: string, orgId: string) => {
return apiClient.get(`/v1/fleet/vehicles?org_id=${orgId}`, { token });
},
updateVehicleDetails: async (vehicleId: string, vehicleData: any, token: string) => {
return apiClient.patch(`/v1/fleet/vehicles/${vehicleId}`, vehicleData, { token });
},
createStaff: async (staffData: any, token: string) => {
return apiClient.post('/v1/fleet/staff', staffData, { token });
},
getStaff: async (token: string, orgId: string) => {
return apiClient.get(`/v1/fleet/staff?organisationId=${orgId}`, { token });
},
createRoster: async (rosterData: any, token: string) => {
return apiClient.post('/v1/fleet/roster', rosterData, { token });
},
startShift: async (shiftData: any, token: string) => {
return apiClient.post('/v1/fleet/shifts/start', shiftData, { token });
},
getInventoryMaster: async (token: string) => {
return apiClient.get('/v1/fleet/inventory/master', { token });
}
};

45
src/api/incidents.ts Normal file
View File

@@ -0,0 +1,45 @@
import { apiClient } from './apiClient';
import type { ApiResponse, Incident } from './types';
export const incidentsApi = {
createIncident: async (incidentData: any, token: string): Promise<ApiResponse<Incident>> => {
return apiClient.post('/v1/incidents', incidentData, { token });
},
getIncidents: async (params: { status?: string; limit?: number; date_from?: string }, token: string): Promise<ApiResponse<Incident[]>> => {
const query = new URLSearchParams();
if (params.status) query.append('status', params.status);
if (params.limit) query.append('limit', params.limit.toString());
if (params.date_from) query.append('date_from', params.date_from);
return apiClient.get(`/v1/incidents?${query.toString()}`, { token });
},
getIncidentById: async (id: string, token: string): Promise<ApiResponse<Incident>> => {
return apiClient.get(`/v1/incidents/${id}`, { token });
},
updateIncident: async (id: string, incidentData: any, token: string): Promise<ApiResponse<Incident>> => {
return apiClient.patch(`/v1/incidents/${id}`, incidentData, { token });
},
getIncidentTimeline: async (id: string, token: string): Promise<ApiResponse<any[]>> => {
return apiClient.get(`/v1/incidents/${id}/timeline`, { token });
},
getIncidentAudit: async (id: string, token: string): Promise<ApiResponse<any[]>> => {
return apiClient.get(`/v1/incidents/${id}/audit`, { token });
},
recommendDispatch: async (payload: { gps_lat: number, gps_lon: number, vehicle_type_required: string }, token: string): Promise<ApiResponse<any>> => {
return apiClient.post('/v1/dispatch/recommend', payload, { token });
},
dispatchVehicle: async (incidentId: string, vehicleId: string, token: string): Promise<ApiResponse<any>> => {
return apiClient.post(`/v1/incidents/${incidentId}/dispatch`, { vehicle_id: vehicleId }, { token });
},
addPatientsToIncident: async (incidentId: string, patients: any[], token: string): Promise<ApiResponse<any>> => {
return apiClient.post(`/v1/incidents/${incidentId}/patients`, { patients }, { token });
}
};

81
src/api/types.ts Normal file
View File

@@ -0,0 +1,81 @@
export type AuthUser = {
id: string;
phone: string;
username: string;
roles: string[];
}
export type LoginResponse = {
status: number;
message: string;
data: {
mfa_required: boolean;
mfa_session_token?: string;
user?: AuthUser;
access_token?: string;
refresh_token?: string;
};
}
export type MfaSetupResponse = {
status: number;
message: string;
data: {
totp_uri: string;
secret: string;
qr_code_base64: string;
};
}
export type MfaVerifyResponse = {
status: number;
message: string;
data: {
backup_codes: string[];
};
meta: {
request_id: string;
timestamp: string;
};
}
export interface Patient {
name: string;
age: number;
gender: string;
triage_level: string;
symptoms: { name: string; duration_minutes: number }[];
}
export interface Incident {
id: string;
category: string;
triage_level: string;
severity: string;
caller_id: string;
organisationId: string | null;
gps_lat: number;
gps_lon: number;
address: string;
patients: Patient[];
notes: string;
guest_name?: string | null;
guest_phone?: string | null;
status: string;
assigned_vehicle: string | null;
eta_seconds: number | null;
createdAt: string;
updatedAt: string;
}
export interface ApiResponse<T> {
status: number;
message: string;
data: T;
meta: {
request_id: string;
timestamp: string;
next_cursor?: string | null;
total_count?: number;
};
}