Improve teh securty of teh DB
This commit is contained in:
141
frontend/src/api/hooks/equipment.ts
Normal file
141
frontend/src/api/hooks/equipment.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
// frontend/src/api/hooks/equipment.ts
|
||||
/**
|
||||
* React hooks for Equipment API integration
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { equipmentService } from '../services/equipment';
|
||||
import type { Equipment } from '../types/equipment';
|
||||
|
||||
// Query Keys
|
||||
export const equipmentKeys = {
|
||||
all: ['equipment'] as const,
|
||||
lists: () => [...equipmentKeys.all, 'list'] as const,
|
||||
list: (tenantId: string, filters?: Record<string, any>) =>
|
||||
[...equipmentKeys.lists(), tenantId, filters] as const,
|
||||
details: () => [...equipmentKeys.all, 'detail'] as const,
|
||||
detail: (tenantId: string, equipmentId: string) =>
|
||||
[...equipmentKeys.details(), tenantId, equipmentId] as const,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to fetch equipment list
|
||||
*/
|
||||
export function useEquipment(
|
||||
tenantId: string,
|
||||
filters?: {
|
||||
status?: string;
|
||||
type?: string;
|
||||
is_active?: boolean;
|
||||
},
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: equipmentKeys.list(tenantId, filters),
|
||||
queryFn: () => equipmentService.getEquipment(tenantId, filters),
|
||||
enabled: !!tenantId && (options?.enabled ?? true),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch a specific equipment item
|
||||
*/
|
||||
export function useEquipmentById(
|
||||
tenantId: string,
|
||||
equipmentId: string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: equipmentKeys.detail(tenantId, equipmentId),
|
||||
queryFn: () => equipmentService.getEquipmentById(tenantId, equipmentId),
|
||||
enabled: !!tenantId && !!equipmentId && (options?.enabled ?? true),
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to create equipment
|
||||
*/
|
||||
export function useCreateEquipment(tenantId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (equipmentData: Equipment) =>
|
||||
equipmentService.createEquipment(tenantId, equipmentData),
|
||||
onSuccess: (newEquipment) => {
|
||||
// Invalidate and refetch equipment lists
|
||||
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
|
||||
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
equipmentKeys.detail(tenantId, newEquipment.id),
|
||||
newEquipment
|
||||
);
|
||||
|
||||
toast.success('Equipment created successfully');
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Error creating equipment:', error);
|
||||
toast.error(error.response?.data?.detail || 'Error creating equipment');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to update equipment
|
||||
*/
|
||||
export function useUpdateEquipment(tenantId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ equipmentId, equipmentData }: {
|
||||
equipmentId: string;
|
||||
equipmentData: Partial<Equipment>;
|
||||
}) => equipmentService.updateEquipment(tenantId, equipmentId, equipmentData),
|
||||
onSuccess: (updatedEquipment, { equipmentId }) => {
|
||||
// Update cached data
|
||||
queryClient.setQueryData(
|
||||
equipmentKeys.detail(tenantId, equipmentId),
|
||||
updatedEquipment
|
||||
);
|
||||
|
||||
// Invalidate lists to refresh
|
||||
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
|
||||
|
||||
toast.success('Equipment updated successfully');
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Error updating equipment:', error);
|
||||
toast.error(error.response?.data?.detail || 'Error updating equipment');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to delete equipment
|
||||
*/
|
||||
export function useDeleteEquipment(tenantId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (equipmentId: string) =>
|
||||
equipmentService.deleteEquipment(tenantId, equipmentId),
|
||||
onSuccess: (_, equipmentId) => {
|
||||
// Remove from cache
|
||||
queryClient.removeQueries({
|
||||
queryKey: equipmentKeys.detail(tenantId, equipmentId)
|
||||
});
|
||||
|
||||
// Invalidate lists to refresh
|
||||
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
|
||||
|
||||
toast.success('Equipment deleted successfully');
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Error deleting equipment:', error);
|
||||
toast.error(error.response?.data?.detail || 'Error deleting equipment');
|
||||
},
|
||||
});
|
||||
}
|
||||
178
frontend/src/api/services/equipment.ts
Normal file
178
frontend/src/api/services/equipment.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
// frontend/src/api/services/equipment.ts
|
||||
/**
|
||||
* Equipment API service
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type {
|
||||
Equipment,
|
||||
EquipmentCreate,
|
||||
EquipmentUpdate,
|
||||
EquipmentResponse,
|
||||
EquipmentListResponse
|
||||
} from '../types/equipment';
|
||||
|
||||
class EquipmentService {
|
||||
private readonly baseURL = '/tenants';
|
||||
|
||||
/**
|
||||
* Helper to convert snake_case API response to camelCase Equipment
|
||||
*/
|
||||
private convertToEquipment(response: EquipmentResponse): Equipment {
|
||||
return {
|
||||
id: response.id,
|
||||
tenant_id: response.tenant_id,
|
||||
name: response.name,
|
||||
type: response.type,
|
||||
model: response.model || '',
|
||||
serialNumber: response.serial_number || '',
|
||||
location: response.location || '',
|
||||
status: response.status,
|
||||
installDate: response.install_date || new Date().toISOString().split('T')[0],
|
||||
lastMaintenance: response.last_maintenance_date || new Date().toISOString().split('T')[0],
|
||||
nextMaintenance: response.next_maintenance_date || new Date().toISOString().split('T')[0],
|
||||
maintenanceInterval: response.maintenance_interval_days || 30,
|
||||
temperature: response.current_temperature || undefined,
|
||||
targetTemperature: response.target_temperature || undefined,
|
||||
efficiency: response.efficiency_percentage || 0,
|
||||
uptime: response.uptime_percentage || 0,
|
||||
energyUsage: response.energy_usage_kwh || 0,
|
||||
utilizationToday: 0, // Not in backend yet
|
||||
alerts: [], // Not in backend yet
|
||||
maintenanceHistory: [], // Not in backend yet
|
||||
specifications: {
|
||||
power: response.power_kw || 0,
|
||||
capacity: response.capacity || 0,
|
||||
dimensions: {
|
||||
width: 0, // Not in backend separately
|
||||
height: 0,
|
||||
depth: 0
|
||||
},
|
||||
weight: response.weight_kg || 0
|
||||
},
|
||||
is_active: response.is_active,
|
||||
created_at: response.created_at,
|
||||
updated_at: response.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to convert Equipment to API request format (snake_case)
|
||||
*/
|
||||
private convertToApiFormat(equipment: Partial<Equipment>): EquipmentCreate | EquipmentUpdate {
|
||||
return {
|
||||
name: equipment.name,
|
||||
type: equipment.type,
|
||||
model: equipment.model,
|
||||
serial_number: equipment.serialNumber,
|
||||
location: equipment.location,
|
||||
status: equipment.status,
|
||||
install_date: equipment.installDate,
|
||||
last_maintenance_date: equipment.lastMaintenance,
|
||||
next_maintenance_date: equipment.nextMaintenance,
|
||||
maintenance_interval_days: equipment.maintenanceInterval,
|
||||
efficiency_percentage: equipment.efficiency,
|
||||
uptime_percentage: equipment.uptime,
|
||||
energy_usage_kwh: equipment.energyUsage,
|
||||
power_kw: equipment.specifications?.power,
|
||||
capacity: equipment.specifications?.capacity,
|
||||
weight_kg: equipment.specifications?.weight,
|
||||
current_temperature: equipment.temperature,
|
||||
target_temperature: equipment.targetTemperature,
|
||||
is_active: equipment.is_active
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all equipment for a tenant
|
||||
*/
|
||||
async getEquipment(
|
||||
tenantId: string,
|
||||
filters?: {
|
||||
status?: string;
|
||||
type?: string;
|
||||
is_active?: boolean;
|
||||
}
|
||||
): Promise<Equipment[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.status) params.append('status', filters.status);
|
||||
if (filters?.type) params.append('type', filters.type);
|
||||
if (filters?.is_active !== undefined) params.append('is_active', String(filters.is_active));
|
||||
|
||||
const queryString = params.toString();
|
||||
const url = `${this.baseURL}/${tenantId}/production/equipment${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
const data: EquipmentListResponse = await apiClient.get(url, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
|
||||
return data.equipment.map(eq => this.convertToEquipment(eq));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific equipment item
|
||||
*/
|
||||
async getEquipmentById(
|
||||
tenantId: string,
|
||||
equipmentId: string
|
||||
): Promise<Equipment> {
|
||||
const data: EquipmentResponse = await apiClient.get(
|
||||
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
return this.convertToEquipment(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new equipment item
|
||||
*/
|
||||
async createEquipment(
|
||||
tenantId: string,
|
||||
equipmentData: Equipment
|
||||
): Promise<Equipment> {
|
||||
const apiData = this.convertToApiFormat(equipmentData);
|
||||
const data: EquipmentResponse = await apiClient.post(
|
||||
`${this.baseURL}/${tenantId}/production/equipment`,
|
||||
apiData,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
return this.convertToEquipment(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an equipment item
|
||||
*/
|
||||
async updateEquipment(
|
||||
tenantId: string,
|
||||
equipmentId: string,
|
||||
equipmentData: Partial<Equipment>
|
||||
): Promise<Equipment> {
|
||||
const apiData = this.convertToApiFormat(equipmentData);
|
||||
const data: EquipmentResponse = await apiClient.put(
|
||||
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
|
||||
apiData,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
return this.convertToEquipment(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an equipment item
|
||||
*/
|
||||
async deleteEquipment(tenantId: string, equipmentId: string): Promise<void> {
|
||||
await apiClient.delete(
|
||||
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const equipmentService = new EquipmentService();
|
||||
@@ -172,9 +172,11 @@ class TrainingService {
|
||||
* Get WebSocket URL for real-time training updates
|
||||
*/
|
||||
getTrainingWebSocketUrl(tenantId: string, jobId: string): string {
|
||||
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL?.replace(/^http/, 'ws');
|
||||
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL
|
||||
?.replace(/^http(s?):/, 'ws$1:'); // http: → ws:, https: → wss:
|
||||
return `${baseWsUrl}/tenants/${tenantId}/training/jobs/${jobId}/live`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to construct WebSocket connection
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface EquipmentSpecifications {
|
||||
|
||||
export interface Equipment {
|
||||
id: string;
|
||||
tenant_id?: string;
|
||||
name: string;
|
||||
type: 'oven' | 'mixer' | 'proofer' | 'freezer' | 'packaging' | 'other';
|
||||
model: string;
|
||||
@@ -51,4 +52,90 @@ export interface Equipment {
|
||||
alerts: EquipmentAlert[];
|
||||
maintenanceHistory: MaintenanceHistory[];
|
||||
specifications: EquipmentSpecifications;
|
||||
is_active?: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
// API Request/Response types
|
||||
export type EquipmentType = 'oven' | 'mixer' | 'proofer' | 'freezer' | 'packaging' | 'other';
|
||||
export type EquipmentStatus = 'operational' | 'maintenance' | 'down' | 'warning';
|
||||
|
||||
export interface EquipmentCreate {
|
||||
name: string;
|
||||
type: EquipmentType;
|
||||
model?: string;
|
||||
serial_number?: string;
|
||||
location?: string;
|
||||
status?: EquipmentStatus;
|
||||
install_date?: string;
|
||||
last_maintenance_date?: string;
|
||||
next_maintenance_date?: string;
|
||||
maintenance_interval_days?: number;
|
||||
efficiency_percentage?: number;
|
||||
uptime_percentage?: number;
|
||||
energy_usage_kwh?: number;
|
||||
power_kw?: number;
|
||||
capacity?: number;
|
||||
weight_kg?: number;
|
||||
current_temperature?: number;
|
||||
target_temperature?: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface EquipmentUpdate {
|
||||
name?: string;
|
||||
type?: EquipmentType;
|
||||
model?: string;
|
||||
serial_number?: string;
|
||||
location?: string;
|
||||
status?: EquipmentStatus;
|
||||
install_date?: string;
|
||||
last_maintenance_date?: string;
|
||||
next_maintenance_date?: string;
|
||||
maintenance_interval_days?: number;
|
||||
efficiency_percentage?: number;
|
||||
uptime_percentage?: number;
|
||||
energy_usage_kwh?: number;
|
||||
power_kw?: number;
|
||||
capacity?: number;
|
||||
weight_kg?: number;
|
||||
current_temperature?: number;
|
||||
target_temperature?: number;
|
||||
is_active?: boolean;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface EquipmentResponse {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
name: string;
|
||||
type: EquipmentType;
|
||||
model: string | null;
|
||||
serial_number: string | null;
|
||||
location: string | null;
|
||||
status: EquipmentStatus;
|
||||
install_date: string | null;
|
||||
last_maintenance_date: string | null;
|
||||
next_maintenance_date: string | null;
|
||||
maintenance_interval_days: number | null;
|
||||
efficiency_percentage: number | null;
|
||||
uptime_percentage: number | null;
|
||||
energy_usage_kwh: number | null;
|
||||
power_kw: number | null;
|
||||
capacity: number | null;
|
||||
weight_kg: number | null;
|
||||
current_temperature: number | null;
|
||||
target_temperature: number | null;
|
||||
is_active: boolean;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface EquipmentListResponse {
|
||||
equipment: EquipmentResponse[];
|
||||
total_count: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
@@ -465,6 +465,12 @@ export interface ProductSuggestionResponse {
|
||||
is_seasonal: boolean;
|
||||
suggested_supplier: string | null;
|
||||
notes: string | null;
|
||||
sales_data?: {
|
||||
total_quantity: number;
|
||||
average_daily_sales: number;
|
||||
peak_day: string;
|
||||
frequency: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BusinessModelAnalysisResponse {
|
||||
|
||||
Reference in New Issue
Block a user