Improve the frontend and fix TODOs
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -655,6 +655,7 @@ export {
|
||||
useUpdateBatchStatus,
|
||||
useProductionDashboardData,
|
||||
useProductionPlanningData,
|
||||
useTriggerProductionScheduler,
|
||||
productionKeys,
|
||||
} from './hooks/production';
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user