Improve the frontend and fix TODOs

This commit is contained in:
Urtzi Alfaro
2025-10-24 13:05:04 +02:00
parent 07c33fa578
commit 61376b7a9f
100 changed files with 8284 additions and 3419 deletions

View File

@@ -196,10 +196,24 @@ export const useStockMovements = (
offset: number = 0,
options?: Omit<UseQueryOptions<StockMovementResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
// Validate UUID format if ingredientId is provided
const isValidUUID = (uuid?: string): boolean => {
if (!uuid) return true; // undefined/null is valid (means no filter)
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
};
const validIngredientId = ingredientId && isValidUUID(ingredientId) ? ingredientId : undefined;
// Log warning if ingredient ID is invalid
if (ingredientId && !isValidUUID(ingredientId)) {
console.warn('[useStockMovements] Invalid ingredient ID format:', ingredientId);
}
return useQuery<StockMovementResponse[], ApiError>({
queryKey: inventoryKeys.stock.movements(tenantId, ingredientId),
queryFn: () => inventoryService.getStockMovements(tenantId, ingredientId, limit, offset),
enabled: !!tenantId,
queryKey: inventoryKeys.stock.movements(tenantId, validIngredientId),
queryFn: () => inventoryService.getStockMovements(tenantId, validIngredientId, limit, offset),
enabled: !!tenantId && (!ingredientId || isValidUUID(ingredientId)),
staleTime: 1 * 60 * 1000, // 1 minute
...options,
});

View File

@@ -230,7 +230,7 @@ export const useProductionPlanningData = (tenantId: string, date?: string) => {
const schedule = useProductionSchedule(tenantId);
const capacity = useCapacityStatus(tenantId, date);
const requirements = useProductionRequirements(tenantId, date);
return {
schedule: schedule.data,
capacity: capacity.data,
@@ -243,4 +243,40 @@ export const useProductionPlanningData = (tenantId: string, date?: string) => {
requirements.refetch();
},
};
};
// ===== Scheduler Mutations =====
/**
* Hook to trigger production scheduler manually (for development/testing)
*/
export const useTriggerProductionScheduler = (
options?: UseMutationOptions<
{ success: boolean; message: string; tenant_id: string },
ApiError,
string
>
) => {
const queryClient = useQueryClient();
return useMutation<
{ success: boolean; message: string; tenant_id: string },
ApiError,
string
>({
mutationFn: (tenantId: string) => productionService.triggerProductionScheduler(tenantId),
onSuccess: (_, tenantId) => {
// Invalidate all production queries for this tenant
queryClient.invalidateQueries({
queryKey: productionKeys.dashboard(tenantId),
});
queryClient.invalidateQueries({
queryKey: productionKeys.batches(tenantId),
});
queryClient.invalidateQueries({
queryKey: productionKeys.activeBatches(tenantId),
});
},
...options,
});
};

View File

@@ -12,6 +12,7 @@ import {
TenantStatistics,
TenantSearchParams,
TenantNearbyParams,
AddMemberWithUserCreate,
} from '../types/tenant';
import { ApiError } from '../client';
@@ -247,16 +248,16 @@ export const useUpdateModelStatus = (
export const useAddTeamMember = (
options?: UseMutationOptions<
TenantMemberResponse,
ApiError,
TenantMemberResponse,
ApiError,
{ tenantId: string; userId: string; role: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
TenantMemberResponse,
ApiError,
TenantMemberResponse,
ApiError,
{ tenantId: string; userId: string; role: string }
>({
mutationFn: ({ tenantId, userId, role }) => tenantService.addTeamMember(tenantId, userId, role),
@@ -268,6 +269,30 @@ export const useAddTeamMember = (
});
};
export const useAddTeamMemberWithUserCreation = (
options?: UseMutationOptions<
TenantMemberResponse,
ApiError,
{ tenantId: string; memberData: AddMemberWithUserCreate }
>
) => {
const queryClient = useQueryClient();
return useMutation<
TenantMemberResponse,
ApiError,
{ tenantId: string; memberData: AddMemberWithUserCreate }
>({
mutationFn: ({ tenantId, memberData }) =>
tenantService.addTeamMemberWithUserCreation(tenantId, memberData),
onSuccess: (data, { tenantId }) => {
// Invalidate team members query
queryClient.invalidateQueries({ queryKey: tenantKeys.members(tenantId) });
},
...options,
});
};
export const useUpdateMemberRole = (
options?: UseMutationOptions<
TenantMemberResponse,

View File

@@ -655,6 +655,7 @@ export {
useUpdateBatchStatus,
useProductionDashboardData,
useProductionPlanningData,
useTriggerProductionScheduler,
productionKeys,
} from './hooks/production';

View File

@@ -405,6 +405,25 @@ export class ProductionService {
return apiClient.get(url);
}
// ===================================================================
// OPERATIONS: Scheduler
// ===================================================================
/**
* Trigger production scheduler manually (for testing/development)
* POST /tenants/{tenant_id}/production/operations/scheduler/trigger
*/
static async triggerProductionScheduler(tenantId: string): Promise<{
success: boolean;
message: string;
tenant_id: string
}> {
return apiClient.post(
`/tenants/${tenantId}/production/operations/scheduler/trigger`,
{}
);
}
}
export const productionService = new ProductionService();

View File

@@ -21,6 +21,7 @@ import {
TenantStatistics,
TenantSearchParams,
TenantNearbyParams,
AddMemberWithUserCreate,
} from '../types/tenant';
export class TenantService {
@@ -125,8 +126,8 @@ export class TenantService {
// Backend: services/tenant/app/api/tenant_members.py
// ===================================================================
async addTeamMember(
tenantId: string,
userId: string,
tenantId: string,
userId: string,
role: string
): Promise<TenantMemberResponse> {
return apiClient.post<TenantMemberResponse>(`${this.baseUrl}/${tenantId}/members`, {
@@ -135,6 +136,16 @@ export class TenantService {
});
}
async addTeamMemberWithUserCreation(
tenantId: string,
memberData: AddMemberWithUserCreate
): Promise<TenantMemberResponse> {
return apiClient.post<TenantMemberResponse>(
`${this.baseUrl}/${tenantId}/members/with-user`,
memberData
);
}
async getTeamMembers(tenantId: string, activeOnly: boolean = true): Promise<TenantMemberResponse[]> {
const queryParams = new URLSearchParams();
queryParams.append('active_only', activeOnly.toString());

View File

@@ -68,13 +68,34 @@ export interface TenantMemberInvitation {
/**
* Schema for updating tenant member
* Backend: TenantMemberUpdate in schemas/tenants.py (lines 132-135)
* Backend: TenantMemberUpdate in schemas/tenants.py (lines 137-140)
*/
export interface TenantMemberUpdate {
role?: 'owner' | 'admin' | 'member' | 'viewer' | null;
is_active?: boolean | null;
}
/**
* Schema for adding member with optional user creation (pilot phase)
* Backend: AddMemberWithUserCreate in schemas/tenants.py (lines 142-174)
*/
export interface AddMemberWithUserCreate {
// For existing users
user_id?: string | null;
// For new user creation
create_user?: boolean; // Default: false
email?: string | null;
full_name?: string | null;
password?: string | null;
phone?: string | null;
language?: 'es' | 'en' | 'eu'; // Default: "es"
timezone?: string; // Default: "Europe/Madrid"
// Common fields
role: 'admin' | 'member' | 'viewer';
}
/**
* Schema for updating tenant subscription
* Backend: TenantSubscriptionUpdate in schemas/tenants.py (lines 137-140)
@@ -121,8 +142,8 @@ export interface TenantAccessResponse {
}
/**
* Tenant member response - FIXED VERSION
* Backend: TenantMemberResponse in schemas/tenants.py (lines 90-107)
* Tenant member response - FIXED VERSION with enriched user data
* Backend: TenantMemberResponse in schemas/tenants.py (lines 91-112)
*/
export interface TenantMemberResponse {
id: string;
@@ -130,6 +151,10 @@ export interface TenantMemberResponse {
role: string;
is_active: boolean;
joined_at?: string | null; // ISO datetime string
// Enriched user fields (populated via service layer)
user_email?: string | null;
user_full_name?: string | null;
user?: any; // Full user object for compatibility
}
/**