Improve the frontend and repository layer

This commit is contained in:
Urtzi Alfaro
2025-10-23 07:44:54 +02:00
parent 8d30172483
commit 07c33fa578
112 changed files with 14726 additions and 2733 deletions

View File

@@ -280,9 +280,7 @@ export const usePOSTransaction = (
tenant_id: string;
transaction_id: string;
},
options?: Omit<UseQueryOptions<{
transaction: POSTransaction;
}, ApiError>, 'queryKey' | 'queryFn'>
options?: Omit<UseQueryOptions<POSTransaction, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: posKeys.transaction(params.tenant_id, params.transaction_id),
@@ -293,6 +291,40 @@ export const usePOSTransaction = (
});
};
/**
* Get POS transactions dashboard summary
*/
export const usePOSTransactionsDashboard = (
params: {
tenant_id: string;
},
options?: Omit<UseQueryOptions<{
total_transactions_today: number;
total_transactions_this_week: number;
total_transactions_this_month: number;
revenue_today: number;
revenue_this_week: number;
revenue_this_month: number;
average_transaction_value: number;
status_breakdown: Record<string, number>;
payment_method_breakdown: Record<string, number>;
sync_status: {
synced: number;
pending: number;
failed: number;
last_sync_at?: string;
};
}, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery({
queryKey: [...posKeys.transactions(), 'dashboard', params.tenant_id],
queryFn: () => posService.getPOSTransactionsDashboard(params),
enabled: !!params.tenant_id,
staleTime: 30 * 1000, // 30 seconds
...options,
});
};
// ============================================================================
// SYNC OPERATIONS
// ============================================================================

View File

@@ -0,0 +1,140 @@
// frontend/src/api/hooks/settings.ts
/**
* React Query hooks for Tenant Settings
* Provides data fetching, caching, and mutation hooks
*/
import { useQuery, useMutation, useQueryClient, UseQueryOptions } from '@tanstack/react-query';
import { settingsApi } from '../services/settings';
import { useToast } from '../../hooks/ui/useToast';
import type {
TenantSettings,
TenantSettingsUpdate,
SettingsCategory,
CategoryResetResponse,
} from '../types/settings';
// Query keys
export const settingsKeys = {
all: ['settings'] as const,
tenant: (tenantId: string) => ['settings', tenantId] as const,
category: (tenantId: string, category: SettingsCategory) =>
['settings', tenantId, category] as const,
};
/**
* Hook to fetch all settings for a tenant
*/
export const useSettings = (
tenantId: string,
options?: Omit<UseQueryOptions<TenantSettings, Error>, 'queryKey' | 'queryFn'>
) => {
return useQuery<TenantSettings, Error>({
queryKey: settingsKeys.tenant(tenantId),
queryFn: () => settingsApi.getSettings(tenantId),
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
/**
* Hook to fetch settings for a specific category
*/
export const useCategorySettings = (
tenantId: string,
category: SettingsCategory,
options?: Omit<UseQueryOptions<Record<string, any>, Error>, 'queryKey' | 'queryFn'>
) => {
return useQuery<Record<string, any>, Error>({
queryKey: settingsKeys.category(tenantId, category),
queryFn: () => settingsApi.getCategorySettings(tenantId, category),
staleTime: 5 * 60 * 1000, // 5 minutes
...options,
});
};
/**
* Hook to update tenant settings
*/
export const useUpdateSettings = () => {
const queryClient = useQueryClient();
const { addToast } = useToast();
return useMutation<
TenantSettings,
Error,
{ tenantId: string; updates: TenantSettingsUpdate }
>({
mutationFn: ({ tenantId, updates }) => settingsApi.updateSettings(tenantId, updates),
onSuccess: (data, variables) => {
// Invalidate all settings queries for this tenant
queryClient.invalidateQueries({ queryKey: settingsKeys.tenant(variables.tenantId) });
addToast('Ajustes guardados correctamente', { type: 'success' });
},
onError: (error) => {
console.error('Failed to update settings:', error);
addToast('Error al guardar los ajustes', { type: 'error' });
},
});
};
/**
* Hook to update a specific category
*/
export const useUpdateCategorySettings = () => {
const queryClient = useQueryClient();
const { addToast } = useToast();
return useMutation<
TenantSettings,
Error,
{ tenantId: string; category: SettingsCategory; settings: Record<string, any> }
>({
mutationFn: ({ tenantId, category, settings }) =>
settingsApi.updateCategorySettings(tenantId, category, settings),
onSuccess: (data, variables) => {
// Invalidate all settings queries for this tenant
queryClient.invalidateQueries({ queryKey: settingsKeys.tenant(variables.tenantId) });
// Also invalidate the specific category query
queryClient.invalidateQueries({
queryKey: settingsKeys.category(variables.tenantId, variables.category),
});
addToast('Ajustes de categoría guardados correctamente', { type: 'success' });
},
onError: (error) => {
console.error('Failed to update category settings:', error);
addToast('Error al guardar los ajustes de categoría', { type: 'error' });
},
});
};
/**
* Hook to reset a category to defaults
*/
export const useResetCategory = () => {
const queryClient = useQueryClient();
const { addToast } = useToast();
return useMutation<
CategoryResetResponse,
Error,
{ tenantId: string; category: SettingsCategory }
>({
mutationFn: ({ tenantId, category }) => settingsApi.resetCategory(tenantId, category),
onSuccess: (data, variables) => {
// Invalidate all settings queries for this tenant
queryClient.invalidateQueries({ queryKey: settingsKeys.tenant(variables.tenantId) });
// Also invalidate the specific category query
queryClient.invalidateQueries({
queryKey: settingsKeys.category(variables.tenantId, variables.category),
});
addToast(`Categoría '${variables.category}' restablecida a valores predeterminados`, {
type: 'success',
});
},
onError: (error) => {
console.error('Failed to reset category:', error);
addToast('Error al restablecer la categoría', { type: 'error' });
},
});
};

View File

@@ -0,0 +1,123 @@
/**
* React Query hooks for Sustainability API
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
getSustainabilityMetrics,
getSustainabilityWidgetData,
getSDGCompliance,
getEnvironmentalImpact,
exportGrantReport
} from '../services/sustainability';
import type {
SustainabilityMetrics,
SustainabilityWidgetData,
SDGCompliance,
EnvironmentalImpact,
GrantReport
} from '../types/sustainability';
// Query keys
export const sustainabilityKeys = {
all: ['sustainability'] as const,
metrics: (tenantId: string, startDate?: string, endDate?: string) =>
['sustainability', 'metrics', tenantId, startDate, endDate] as const,
widget: (tenantId: string, days: number) =>
['sustainability', 'widget', tenantId, days] as const,
sdg: (tenantId: string) =>
['sustainability', 'sdg', tenantId] as const,
environmental: (tenantId: string, days: number) =>
['sustainability', 'environmental', tenantId, days] as const,
};
/**
* Hook to get comprehensive sustainability metrics
*/
export function useSustainabilityMetrics(
tenantId: string,
startDate?: string,
endDate?: string,
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: sustainabilityKeys.metrics(tenantId, startDate, endDate),
queryFn: () => getSustainabilityMetrics(tenantId, startDate, endDate),
enabled: options?.enabled !== false && !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
refetchInterval: 10 * 60 * 1000, // Refetch every 10 minutes
});
}
/**
* Hook to get sustainability widget data (simplified metrics)
*/
export function useSustainabilityWidget(
tenantId: string,
days: number = 30,
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: sustainabilityKeys.widget(tenantId, days),
queryFn: () => getSustainabilityWidgetData(tenantId, days),
enabled: options?.enabled !== false && !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
refetchInterval: 10 * 60 * 1000, // Refetch every 10 minutes
});
}
/**
* Hook to get SDG 12.3 compliance status
*/
export function useSDGCompliance(
tenantId: string,
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: sustainabilityKeys.sdg(tenantId),
queryFn: () => getSDGCompliance(tenantId),
enabled: options?.enabled !== false && !!tenantId,
staleTime: 10 * 60 * 1000, // 10 minutes
});
}
/**
* Hook to get environmental impact data
*/
export function useEnvironmentalImpact(
tenantId: string,
days: number = 30,
options?: { enabled?: boolean }
) {
return useQuery({
queryKey: sustainabilityKeys.environmental(tenantId, days),
queryFn: () => getEnvironmentalImpact(tenantId, days),
enabled: options?.enabled !== false && !!tenantId,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
/**
* Hook to export grant report
*/
export function useExportGrantReport() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
tenantId,
grantType,
startDate,
endDate
}: {
tenantId: string;
grantType?: string;
startDate?: string;
endDate?: string;
}) => exportGrantReport(tenantId, grantType, startDate, endDate),
onSuccess: () => {
// Optionally invalidate related queries
queryClient.invalidateQueries({ queryKey: sustainabilityKeys.all });
},
});
}

View File

@@ -250,12 +250,38 @@ export class POSService {
async getPOSTransaction(params: {
tenant_id: string;
transaction_id: string;
}): Promise<{
transaction: POSTransaction;
}> {
}): Promise<POSTransaction> {
const { tenant_id, transaction_id } = params;
const url = `/tenants/${tenant_id}${this.basePath}/transactions/${transaction_id}`;
return apiClient.get(url);
}
/**
* Get POS transactions dashboard summary
*/
async getPOSTransactionsDashboard(params: {
tenant_id: string;
}): Promise<{
total_transactions_today: number;
total_transactions_this_week: number;
total_transactions_this_month: number;
revenue_today: number;
revenue_this_week: number;
revenue_this_month: number;
average_transaction_value: number;
status_breakdown: Record<string, number>;
payment_method_breakdown: Record<string, number>;
sync_status: {
synced: number;
pending: number;
failed: number;
last_sync_at?: string;
};
}> {
const { tenant_id } = params;
const url = `/tenants/${tenant_id}${this.basePath}/operations/transactions-dashboard`;
return apiClient.get(url);
}

View File

@@ -0,0 +1,152 @@
// frontend/src/api/services/settings.ts
/**
* API service for Tenant Settings
* Handles all HTTP requests for tenant operational configuration
*/
import { apiClient } from '../client/apiClient';
import type {
TenantSettings,
TenantSettingsUpdate,
SettingsCategory,
CategoryResetResponse,
} from '../types/settings';
export const settingsApi = {
/**
* Get all settings for a tenant
*/
getSettings: async (tenantId: string): Promise<TenantSettings> => {
try {
console.log('🔍 Fetching settings for tenant:', tenantId);
const response = await apiClient.get<TenantSettings>(`/tenants/${tenantId}/settings`);
console.log('📊 Settings API response data:', response);
// Validate the response data structure
if (!response) {
throw new Error('Settings response data is null or undefined');
}
if (!response.tenant_id) {
throw new Error('Settings response missing tenant_id');
}
if (!response.procurement_settings) {
throw new Error('Settings response missing procurement_settings');
}
console.log('✅ Settings data validation passed');
return response;
} catch (error) {
console.error('❌ Error fetching settings:', error);
console.error('Error details:', {
message: (error as Error).message,
stack: (error as Error).stack,
tenantId
});
throw error;
}
},
/**
* Update tenant settings (partial update supported)
*/
updateSettings: async (
tenantId: string,
updates: TenantSettingsUpdate
): Promise<TenantSettings> => {
try {
console.log('🔍 Updating settings for tenant:', tenantId, 'with updates:', updates);
const response = await apiClient.put<TenantSettings>(`/tenants/${tenantId}/settings`, updates);
console.log('📊 Settings update response:', response);
if (!response) {
throw new Error('Settings update response data is null or undefined');
}
return response;
} catch (error) {
console.error('❌ Error updating settings:', error);
throw error;
}
},
/**
* Get settings for a specific category
*/
getCategorySettings: async (
tenantId: string,
category: SettingsCategory
): Promise<Record<string, any>> => {
try {
console.log('🔍 Fetching category settings for tenant:', tenantId, 'category:', category);
const response = await apiClient.get<{ tenant_id: string; category: string; settings: Record<string, any> }>(
`/tenants/${tenantId}/settings/${category}`
);
console.log('📊 Category settings response:', response);
if (!response || !response.settings) {
throw new Error('Category settings response data is null or undefined');
}
return response.settings;
} catch (error) {
console.error('❌ Error fetching category settings:', error);
throw error;
}
},
/**
* Update settings for a specific category
*/
updateCategorySettings: async (
tenantId: string,
category: SettingsCategory,
settings: Record<string, any>
): Promise<TenantSettings> => {
try {
console.log('🔍 Updating category settings for tenant:', tenantId, 'category:', category, 'settings:', settings);
const response = await apiClient.put<TenantSettings>(
`/tenants/${tenantId}/settings/${category}`,
{ settings }
);
console.log('📊 Category settings update response:', response);
if (!response) {
throw new Error('Category settings update response data is null or undefined');
}
return response;
} catch (error) {
console.error('❌ Error updating category settings:', error);
throw error;
}
},
/**
* Reset a category to default values
*/
resetCategory: async (
tenantId: string,
category: SettingsCategory
): Promise<CategoryResetResponse> => {
try {
console.log('🔍 Resetting category for tenant:', tenantId, 'category:', category);
const response = await apiClient.post<CategoryResetResponse>(
`/tenants/${tenantId}/settings/${category}/reset`
);
console.log('📊 Category reset response:', response);
if (!response) {
throw new Error('Category reset response data is null or undefined');
}
return response;
} catch (error) {
console.error('❌ Error resetting category:', error);
throw error;
}
},
};
export default settingsApi;

View File

@@ -0,0 +1,85 @@
/**
* Sustainability API Service
* Environmental impact, SDG compliance, and grant reporting
*/
import apiClient from '../client/apiClient';
import type {
SustainabilityMetrics,
SustainabilityWidgetData,
SDGCompliance,
EnvironmentalImpact,
GrantReport
} from '../types/sustainability';
const BASE_PATH = '/sustainability';
/**
* Get comprehensive sustainability metrics
*/
export async function getSustainabilityMetrics(
tenantId: string,
startDate?: string,
endDate?: string
): Promise<SustainabilityMetrics> {
const params = new URLSearchParams();
if (startDate) params.append('start_date', startDate);
if (endDate) params.append('end_date', endDate);
const queryString = params.toString();
const url = `/tenants/${tenantId}${BASE_PATH}/metrics${queryString ? `?${queryString}` : ''}`;
return await apiClient.get<SustainabilityMetrics>(url);
}
/**
* Get simplified sustainability widget data
*/
export async function getSustainabilityWidgetData(
tenantId: string,
days: number = 30
): Promise<SustainabilityWidgetData> {
return await apiClient.get<SustainabilityWidgetData>(
`/tenants/${tenantId}${BASE_PATH}/widget?days=${days}`
);
}
/**
* Get SDG 12.3 compliance status
*/
export async function getSDGCompliance(tenantId: string): Promise<SDGCompliance> {
return await apiClient.get<SDGCompliance>(
`/tenants/${tenantId}${BASE_PATH}/sdg-compliance`
);
}
/**
* Get environmental impact metrics
*/
export async function getEnvironmentalImpact(
tenantId: string,
days: number = 30
): Promise<EnvironmentalImpact> {
return await apiClient.get<EnvironmentalImpact>(
`/tenants/${tenantId}${BASE_PATH}/environmental-impact?days=${days}`
);
}
/**
* Export grant application report
*/
export async function exportGrantReport(
tenantId: string,
grantType: string = 'general',
startDate?: string,
endDate?: string
): Promise<GrantReport> {
const payload: any = { grant_type: grantType, format: 'json' };
if (startDate) payload.start_date = startDate;
if (endDate) payload.end_date = endDate;
return await apiClient.post<GrantReport>(
`/tenants/${tenantId}${BASE_PATH}/export/grant-report`,
payload
);
}

View File

@@ -0,0 +1,117 @@
// frontend/src/api/types/settings.ts
/**
* TypeScript types for Tenant Settings
* Operational configuration for bakery tenants
*/
export interface ProcurementSettings {
auto_approve_enabled: boolean;
auto_approve_threshold_eur: number;
auto_approve_min_supplier_score: number;
require_approval_new_suppliers: boolean;
require_approval_critical_items: boolean;
procurement_lead_time_days: number;
demand_forecast_days: number;
safety_stock_percentage: number;
po_approval_reminder_hours: number;
po_critical_escalation_hours: number;
}
export interface InventorySettings {
low_stock_threshold: number;
reorder_point: number;
reorder_quantity: number;
expiring_soon_days: number;
expiration_warning_days: number;
quality_score_threshold: number;
temperature_monitoring_enabled: boolean;
refrigeration_temp_min: number;
refrigeration_temp_max: number;
freezer_temp_min: number;
freezer_temp_max: number;
room_temp_min: number;
room_temp_max: number;
temp_deviation_alert_minutes: number;
critical_temp_deviation_minutes: number;
}
export interface ProductionSettings {
planning_horizon_days: number;
minimum_batch_size: number;
maximum_batch_size: number;
production_buffer_percentage: number;
working_hours_per_day: number;
max_overtime_hours: number;
capacity_utilization_target: number;
capacity_warning_threshold: number;
quality_check_enabled: boolean;
minimum_yield_percentage: number;
quality_score_threshold: number;
schedule_optimization_enabled: boolean;
prep_time_buffer_minutes: number;
cleanup_time_buffer_minutes: number;
labor_cost_per_hour_eur: number;
overhead_cost_percentage: number;
}
export interface SupplierSettings {
default_payment_terms_days: number;
default_delivery_days: number;
excellent_delivery_rate: number;
good_delivery_rate: number;
excellent_quality_rate: number;
good_quality_rate: number;
critical_delivery_delay_hours: number;
critical_quality_rejection_rate: number;
high_cost_variance_percentage: number;
}
export interface POSSettings {
sync_interval_minutes: number;
auto_sync_products: boolean;
auto_sync_transactions: boolean;
}
export interface OrderSettings {
max_discount_percentage: number;
default_delivery_window_hours: number;
dynamic_pricing_enabled: boolean;
discount_enabled: boolean;
delivery_tracking_enabled: boolean;
}
export interface TenantSettings {
id: string;
tenant_id: string;
procurement_settings: ProcurementSettings;
inventory_settings: InventorySettings;
production_settings: ProductionSettings;
supplier_settings: SupplierSettings;
pos_settings: POSSettings;
order_settings: OrderSettings;
created_at: string;
updated_at: string;
}
export interface TenantSettingsUpdate {
procurement_settings?: Partial<ProcurementSettings>;
inventory_settings?: Partial<InventorySettings>;
production_settings?: Partial<ProductionSettings>;
supplier_settings?: Partial<SupplierSettings>;
pos_settings?: Partial<POSSettings>;
order_settings?: Partial<OrderSettings>;
}
export type SettingsCategory =
| 'procurement'
| 'inventory'
| 'production'
| 'supplier'
| 'pos'
| 'order';
export interface CategoryResetResponse {
category: string;
settings: Record<string, any>;
message: string;
}

View File

@@ -0,0 +1,161 @@
/**
* Sustainability TypeScript Types
* Environmental impact, SDG compliance, and grant reporting
*/
export interface PeriodInfo {
start_date: string;
end_date: string;
days: number;
}
export interface WasteMetrics {
total_waste_kg: number;
production_waste_kg: number;
expired_waste_kg: number;
waste_percentage: number;
waste_by_reason: Record<string, number>;
}
export interface CO2Emissions {
kg: number;
tons: number;
trees_to_offset: number;
}
export interface WaterFootprint {
liters: number;
cubic_meters: number;
}
export interface LandUse {
square_meters: number;
hectares: number;
}
export interface HumanEquivalents {
car_km_equivalent: number;
smartphone_charges: number;
showers_equivalent: number;
trees_planted: number;
}
export interface EnvironmentalImpact {
co2_emissions: CO2Emissions;
water_footprint: WaterFootprint;
land_use: LandUse;
human_equivalents: HumanEquivalents;
}
export interface SDG123Metrics {
baseline_waste_percentage: number;
current_waste_percentage: number;
reduction_achieved: number;
target_reduction: number;
progress_to_target: number;
status: 'sdg_compliant' | 'on_track' | 'progressing' | 'baseline';
status_label: string;
target_waste_percentage: number;
}
export interface SDGCompliance {
sdg_12_3: SDG123Metrics;
baseline_period: string;
certification_ready: boolean;
improvement_areas: string[];
}
export interface EnvironmentalImpactAvoided {
co2_kg: number;
water_liters: number;
}
export interface AvoidedWaste {
waste_avoided_kg: number;
ai_assisted_batches: number;
environmental_impact_avoided: EnvironmentalImpactAvoided;
methodology: string;
}
export interface FinancialImpact {
waste_cost_eur: number;
cost_per_kg: number;
potential_monthly_savings: number;
annual_projection: number;
}
export interface GrantProgramEligibility {
eligible: boolean;
confidence: 'high' | 'medium' | 'low';
requirements_met: boolean;
}
export interface GrantReadiness {
overall_readiness_percentage: number;
grant_programs: Record<string, GrantProgramEligibility>;
recommended_applications: string[];
}
export interface SustainabilityMetrics {
period: PeriodInfo;
waste_metrics: WasteMetrics;
environmental_impact: EnvironmentalImpact;
sdg_compliance: SDGCompliance;
avoided_waste: AvoidedWaste;
financial_impact: FinancialImpact;
grant_readiness: GrantReadiness;
}
export interface SustainabilityWidgetData {
total_waste_kg: number;
waste_reduction_percentage: number;
co2_saved_kg: number;
water_saved_liters: number;
trees_equivalent: number;
sdg_status: string;
sdg_progress: number;
grant_programs_ready: number;
financial_savings_eur: number;
}
// Grant Report Types
export interface BaselineComparison {
baseline: number;
current: number;
improvement: number;
}
export interface SupportingData {
baseline_comparison: BaselineComparison;
environmental_benefits: EnvironmentalImpact;
financial_benefits: FinancialImpact;
}
export interface Certifications {
sdg_12_3_compliant: boolean;
grant_programs_eligible: string[];
}
export interface ExecutiveSummary {
total_waste_reduced_kg: number;
waste_reduction_percentage: number;
co2_emissions_avoided_kg: number;
financial_savings_eur: number;
sdg_compliance_status: string;
}
export interface ReportMetadata {
generated_at: string;
report_type: string;
period: PeriodInfo;
tenant_id: string;
}
export interface GrantReport {
report_metadata: ReportMetadata;
executive_summary: ExecutiveSummary;
detailed_metrics: SustainabilityMetrics;
certifications: Certifications;
supporting_data: SupportingData;
}