fix demo session 3
This commit is contained in:
@@ -10,6 +10,8 @@ import { Package, AlertTriangle, CheckCircle2, Activity, Clock, Warehouse, Shopp
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import StatusCard from '../ui/StatusCard/StatusCard';
|
import StatusCard from '../ui/StatusCard/StatusCard';
|
||||||
import { useSSEEvents } from '../../hooks/useSSE';
|
import { useSSEEvents } from '../../hooks/useSSE';
|
||||||
|
import { useChildTenants } from '../../api/hooks/useEnterpriseDashboard';
|
||||||
|
import { inventoryService } from '../../api/services/inventory';
|
||||||
|
|
||||||
interface OutletFulfillmentTabProps {
|
interface OutletFulfillmentTabProps {
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
@@ -21,63 +23,24 @@ const OutletFulfillmentTab: React.FC<OutletFulfillmentTabProps> = ({ tenantId, o
|
|||||||
const [selectedOutlet, setSelectedOutlet] = useState<string | null>(null);
|
const [selectedOutlet, setSelectedOutlet] = useState<string | null>(null);
|
||||||
const [viewMode, setViewMode] = useState<'summary' | 'detailed'>('summary');
|
const [viewMode, setViewMode] = useState<'summary' | 'detailed'>('summary');
|
||||||
|
|
||||||
|
// Get child tenants data
|
||||||
|
const { data: childTenants, isLoading: isChildTenantsLoading } = useChildTenants(tenantId);
|
||||||
|
|
||||||
|
// State for real-time inventory data
|
||||||
|
const [inventoryData, setInventoryData] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
// Combine loading states
|
||||||
|
const isLoading = isChildTenantsLoading || loading;
|
||||||
|
|
||||||
// Real-time SSE events
|
// Real-time SSE events
|
||||||
const { events: sseEvents, isConnected: sseConnected } = useSSEEvents({
|
const { events: sseEvents, isConnected: sseConnected } = useSSEEvents({
|
||||||
channels: ['*.alerts', '*.notifications', 'recommendations']
|
channels: ['*.alerts', '*.notifications', 'recommendations']
|
||||||
});
|
});
|
||||||
|
|
||||||
// State for real-time inventory data
|
|
||||||
const [inventoryData, setInventoryData] = useState([
|
|
||||||
{
|
|
||||||
id: 'outlet-madrid',
|
|
||||||
name: 'Madrid Central',
|
|
||||||
inventoryCoverage: 85,
|
|
||||||
stockoutRisk: 'low',
|
|
||||||
criticalItems: 2,
|
|
||||||
fulfillmentRate: 98,
|
|
||||||
lastUpdated: '2024-01-15T10:30:00',
|
|
||||||
status: 'normal',
|
|
||||||
products: [
|
|
||||||
{ id: 'baguette', name: 'Baguette', coverage: 92, risk: 'low', stock: 450, safetyStock: 300 },
|
|
||||||
{ id: 'croissant', name: 'Croissant', coverage: 78, risk: 'medium', stock: 280, safetyStock: 250 },
|
|
||||||
{ id: 'pain-au-chocolat', name: 'Pain au Chocolat', coverage: 65, risk: 'high', stock: 180, safetyStock: 200 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'outlet-barcelona',
|
|
||||||
name: 'Barcelona Coastal',
|
|
||||||
inventoryCoverage: 68,
|
|
||||||
stockoutRisk: 'medium',
|
|
||||||
criticalItems: 5,
|
|
||||||
fulfillmentRate: 92,
|
|
||||||
lastUpdated: '2024-01-15T10:25:00',
|
|
||||||
status: 'warning',
|
|
||||||
products: [
|
|
||||||
{ id: 'baguette', name: 'Baguette', coverage: 75, risk: 'medium', stock: 320, safetyStock: 300 },
|
|
||||||
{ id: 'croissant', name: 'Croissant', coverage: 58, risk: 'high', stock: 220, safetyStock: 250 },
|
|
||||||
{ id: 'ensaimada', name: 'Ensaimada', coverage: 45, risk: 'critical', stock: 120, safetyStock: 200 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'outlet-valencia',
|
|
||||||
name: 'Valencia Port',
|
|
||||||
inventoryCoverage: 72,
|
|
||||||
stockoutRisk: 'medium',
|
|
||||||
criticalItems: 3,
|
|
||||||
fulfillmentRate: 95,
|
|
||||||
lastUpdated: '2024-01-15T10:20:00',
|
|
||||||
status: 'warning',
|
|
||||||
products: [
|
|
||||||
{ id: 'baguette', name: 'Baguette', coverage: 88, risk: 'low', stock: 420, safetyStock: 300 },
|
|
||||||
{ id: 'croissant', name: 'Croissant', coverage: 65, risk: 'medium', stock: 240, safetyStock: 250 },
|
|
||||||
{ id: 'focaccia', name: 'Focaccia', coverage: 55, risk: 'high', stock: 160, safetyStock: 200 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Process SSE events for inventory updates
|
// Process SSE events for inventory updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sseEvents.length === 0) return;
|
if (sseEvents.length === 0 || inventoryData.length === 0) return;
|
||||||
|
|
||||||
// Filter inventory-related events
|
// Filter inventory-related events
|
||||||
const inventoryEvents = sseEvents.filter(event =>
|
const inventoryEvents = sseEvents.filter(event =>
|
||||||
@@ -145,7 +108,84 @@ const OutletFulfillmentTab: React.FC<OutletFulfillmentTabProps> = ({ tenantId, o
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [sseEvents]);
|
}, [sseEvents, inventoryData]);
|
||||||
|
|
||||||
|
// Fetch inventory data for each child tenant individually
|
||||||
|
useEffect(() => {
|
||||||
|
if (!childTenants) {
|
||||||
|
setInventoryData([]);
|
||||||
|
setLoading(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchAllInventoryData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const promises = childTenants.map(async (tenant) => {
|
||||||
|
try {
|
||||||
|
// Using the imported service directly
|
||||||
|
const inventoryData = await inventoryService.getDashboardSummary(tenant.id);
|
||||||
|
return { tenant, inventoryData };
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching inventory for tenant ${tenant.id}:`, error);
|
||||||
|
return { tenant, inventoryData: null };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
|
||||||
|
const processedData = results.map(({ tenant, inventoryData }) => {
|
||||||
|
// Calculate inventory metrics
|
||||||
|
const totalValue = inventoryData?.total_value || 0;
|
||||||
|
const outOfStockCount = inventoryData?.out_of_stock_count || 0;
|
||||||
|
const lowStockCount = inventoryData?.low_stock_count || 0;
|
||||||
|
const adequateStockCount = inventoryData?.adequate_stock_count || 0;
|
||||||
|
const totalIngredients = inventoryData?.total_ingredients || 0;
|
||||||
|
|
||||||
|
// Calculate coverage percentage (simplified calculation)
|
||||||
|
const coverage = totalIngredients > 0
|
||||||
|
? Math.min(100, Math.round(((adequateStockCount + lowStockCount) / totalIngredients) * 100))
|
||||||
|
: 100;
|
||||||
|
|
||||||
|
// Determine risk level based on out-of-stock and low-stock items
|
||||||
|
let riskLevel = 'low';
|
||||||
|
if (outOfStockCount > 5 || (outOfStockCount > 0 && lowStockCount > 10)) {
|
||||||
|
riskLevel = 'critical';
|
||||||
|
} else if (outOfStockCount > 0 || lowStockCount > 5) {
|
||||||
|
riskLevel = 'high';
|
||||||
|
} else if (lowStockCount > 2) {
|
||||||
|
riskLevel = 'medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine status based on risk level
|
||||||
|
let status = 'normal';
|
||||||
|
if (riskLevel === 'critical') status = 'critical';
|
||||||
|
else if (riskLevel === 'high' || riskLevel === 'medium') status = 'warning';
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: tenant.id,
|
||||||
|
name: tenant.name,
|
||||||
|
inventoryCoverage: coverage,
|
||||||
|
stockoutRisk: riskLevel,
|
||||||
|
criticalItems: outOfStockCount,
|
||||||
|
fulfillmentRate: 95, // Placeholder - would come from actual fulfillment data
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
status: status,
|
||||||
|
products: [] // Will be populated if detailed view is needed
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setInventoryData(processedData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching inventory data:', error);
|
||||||
|
setInventoryData([]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAllInventoryData();
|
||||||
|
}, [childTenants]);
|
||||||
|
|
||||||
// Calculate network-wide fulfillment metrics
|
// Calculate network-wide fulfillment metrics
|
||||||
const calculateNetworkMetrics = () => {
|
const calculateNetworkMetrics = () => {
|
||||||
@@ -336,6 +376,24 @@ const OutletFulfillmentTab: React.FC<OutletFulfillmentTabProps> = ({ tenantId, o
|
|||||||
<Warehouse className="w-5 h-5 text-[var(--color-primary)]" />
|
<Warehouse className="w-5 h-5 text-[var(--color-primary)]" />
|
||||||
{t('enterprise.outlet_status_overview')}
|
{t('enterprise.outlet_status_overview')}
|
||||||
</h3>
|
</h3>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{[...Array(3)].map((_, index) => (
|
||||||
|
<Card key={index} className="animate-pulse">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="h-4 bg-[var(--bg-tertiary)] rounded w-3/4"></div>
|
||||||
|
<div className="w-8 h-8 rounded-full bg-[var(--bg-tertiary)]"></div>
|
||||||
|
</div>
|
||||||
|
<div className="h-6 bg-[var(--bg-tertiary)] rounded w-1/2 mb-2"></div>
|
||||||
|
<div className="h-4 bg-[var(--bg-tertiary)] rounded w-full mb-4"></div>
|
||||||
|
<div className="h-2 bg-[var(--bg-tertiary)] rounded w-full mb-2"></div>
|
||||||
|
<div className="h-2 bg-[var(--bg-tertiary)] rounded w-3/4"></div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{inventoryData.map((outlet) => {
|
{inventoryData.map((outlet) => {
|
||||||
const statusConfig = getOutletStatusConfig(outlet.id);
|
const statusConfig = getOutletStatusConfig(outlet.id);
|
||||||
@@ -385,8 +443,17 @@ const OutletFulfillmentTab: React.FC<OutletFulfillmentTabProps> = ({ tenantId, o
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{inventoryData.length === 0 && (
|
||||||
|
<div className="col-span-full py-12">
|
||||||
|
<div className="text-center text-[var(--text-secondary)]">
|
||||||
|
<Warehouse className="w-12 h-12 mx-auto mb-4 opacity-20" />
|
||||||
|
<p>{t('enterprise.no_outlets')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Detailed View - Product Level Inventory */}
|
{/* Detailed View - Product Level Inventory */}
|
||||||
{viewMode === 'detailed' && selectedOutlet && (
|
{viewMode === 'detailed' && selectedOutlet && (
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ProductionStatusBlock } from './blocks/ProductionStatusBlock';
|
|||||||
import StatusCard from '../ui/StatusCard/StatusCard';
|
import StatusCard from '../ui/StatusCard/StatusCard';
|
||||||
import { useControlPanelData } from '../../api/hooks/useControlPanelData';
|
import { useControlPanelData } from '../../api/hooks/useControlPanelData';
|
||||||
import { useSSEEvents } from '../../hooks/useSSE';
|
import { useSSEEvents } from '../../hooks/useSSE';
|
||||||
|
import { equipmentService } from '../../api/services/equipment';
|
||||||
|
|
||||||
interface ProductionTabProps {
|
interface ProductionTabProps {
|
||||||
tenantId: string;
|
tenantId: string;
|
||||||
@@ -31,52 +32,42 @@ const ProductionTab: React.FC<ProductionTabProps> = ({ tenantId }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// State for equipment data with real-time updates
|
// State for equipment data with real-time updates
|
||||||
const [equipmentData, setEquipmentData] = useState([
|
const [equipmentData, setEquipmentData] = useState<any[]>([]);
|
||||||
{
|
const [equipmentLoading, setEquipmentLoading] = useState(true);
|
||||||
id: 'oven-1',
|
|
||||||
name: 'Oven #1',
|
// Fetch equipment data
|
||||||
status: 'normal',
|
useEffect(() => {
|
||||||
temperature: '180°C',
|
const fetchEquipmentData = async () => {
|
||||||
utilization: 85,
|
setEquipmentLoading(true);
|
||||||
lastMaintenance: '2024-01-15',
|
try {
|
||||||
nextMaintenance: '2024-02-15',
|
const equipmentList = await equipmentService.getEquipment(tenantId);
|
||||||
lastEvent: null
|
// Transform the equipment data to match the expected format
|
||||||
},
|
const transformedData = equipmentList.map(eq => ({
|
||||||
{
|
id: eq.id,
|
||||||
id: 'oven-2',
|
name: eq.name,
|
||||||
name: 'Oven #2',
|
status: eq.status.toLowerCase(),
|
||||||
status: 'warning',
|
temperature: eq.currentTemperature ? `${eq.currentTemperature}°C` : 'N/A',
|
||||||
temperature: '195°C',
|
utilization: eq.efficiency || eq.uptime || 0,
|
||||||
utilization: 92,
|
lastMaintenance: eq.lastMaintenance || new Date().toISOString().split('T')[0],
|
||||||
lastMaintenance: '2024-01-10',
|
nextMaintenance: eq.nextMaintenance || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // Default to 30 days from now
|
||||||
nextMaintenance: '2024-02-10',
|
|
||||||
lastEvent: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mixer-1',
|
|
||||||
name: 'Industrial Mixer',
|
|
||||||
status: 'normal',
|
|
||||||
temperature: 'N/A',
|
|
||||||
utilization: 78,
|
|
||||||
lastMaintenance: '2024-01-20',
|
|
||||||
nextMaintenance: '2024-03-20',
|
|
||||||
lastEvent: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'proofer',
|
|
||||||
name: 'Proofing Chamber',
|
|
||||||
status: 'critical',
|
|
||||||
temperature: '32°C',
|
|
||||||
utilization: 65,
|
|
||||||
lastMaintenance: '2023-12-01',
|
|
||||||
nextMaintenance: '2024-01-31',
|
|
||||||
lastEvent: null
|
lastEvent: null
|
||||||
|
}));
|
||||||
|
setEquipmentData(transformedData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching equipment data:', error);
|
||||||
|
// Set empty array but still mark loading as false
|
||||||
|
setEquipmentData([]);
|
||||||
|
} finally {
|
||||||
|
setEquipmentLoading(false);
|
||||||
}
|
}
|
||||||
]);
|
};
|
||||||
|
|
||||||
|
fetchEquipmentData();
|
||||||
|
}, [tenantId]);
|
||||||
|
|
||||||
// Process SSE events for equipment status updates
|
// Process SSE events for equipment status updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sseEvents.length === 0) return;
|
if (sseEvents.length === 0 || equipmentData.length === 0) return;
|
||||||
|
|
||||||
// Filter equipment-related events
|
// Filter equipment-related events
|
||||||
const equipmentEvents = sseEvents.filter(event =>
|
const equipmentEvents = sseEvents.filter(event =>
|
||||||
@@ -143,7 +134,7 @@ const ProductionTab: React.FC<ProductionTabProps> = ({ tenantId }) => {
|
|||||||
return equipment;
|
return equipment;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [sseEvents]);
|
}, [sseEvents, equipmentData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@@ -170,6 +161,24 @@ const ProductionTab: React.FC<ProductionTabProps> = ({ tenantId }) => {
|
|||||||
<Cog className="w-6 h-6 text-[var(--color-secondary)]" />
|
<Cog className="w-6 h-6 text-[var(--color-secondary)]" />
|
||||||
{t('production.equipment_status')}
|
{t('production.equipment_status')}
|
||||||
</h2>
|
</h2>
|
||||||
|
{equipmentLoading ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
{[...Array(4)].map((_, index) => (
|
||||||
|
<Card key={index} className="animate-pulse">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div className="h-4 bg-[var(--bg-tertiary)] rounded w-3/4"></div>
|
||||||
|
<div className="w-8 h-8 rounded-full bg-[var(--bg-tertiary)]"></div>
|
||||||
|
</div>
|
||||||
|
<div className="h-6 bg-[var(--bg-tertiary)] rounded w-1/2 mb-2"></div>
|
||||||
|
<div className="h-4 bg-[var(--bg-tertiary)] rounded w-full mb-4"></div>
|
||||||
|
<div className="h-2 bg-[var(--bg-tertiary)] rounded w-full mb-2"></div>
|
||||||
|
<div className="h-2 bg-[var(--bg-tertiary)] rounded w-3/4"></div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{equipmentData.map((equipment) => {
|
{equipmentData.map((equipment) => {
|
||||||
// Determine status configuration
|
// Determine status configuration
|
||||||
@@ -212,7 +221,7 @@ const ProductionTab: React.FC<ProductionTabProps> = ({ tenantId }) => {
|
|||||||
|
|
||||||
// Add SSE connection status to first card
|
// Add SSE connection status to first card
|
||||||
const additionalMetadata = [];
|
const additionalMetadata = [];
|
||||||
if (equipment.id === 'oven-1') {
|
if (equipment.id === equipmentData[0]?.id) {
|
||||||
additionalMetadata.push(
|
additionalMetadata.push(
|
||||||
sseConnected
|
sseConnected
|
||||||
? `🟢 ${t('enterprise.live_updates')}`
|
? `🟢 ${t('enterprise.live_updates')}`
|
||||||
@@ -260,8 +269,17 @@ const ProductionTab: React.FC<ProductionTabProps> = ({ tenantId }) => {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{equipmentData.length === 0 && !equipmentLoading && (
|
||||||
|
<div className="col-span-full py-12">
|
||||||
|
<div className="text-center text-[var(--text-secondary)]">
|
||||||
|
<Cog className="w-12 h-12 mx-auto mb-4 opacity-20" />
|
||||||
|
<p>{t('production.no_equipment')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Production Efficiency Metrics */}
|
{/* Production Efficiency Metrics */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
"status_critical": "Critical",
|
"status_critical": "Critical",
|
||||||
"status_warning": "Warning",
|
"status_warning": "Warning",
|
||||||
"status_normal": "Normal",
|
"status_normal": "Normal",
|
||||||
|
"no_equipment": "No equipment available",
|
||||||
"efficiency_metrics": "Production Efficiency Metrics",
|
"efficiency_metrics": "Production Efficiency Metrics",
|
||||||
"on_time_start_rate": "On-time Start Rate",
|
"on_time_start_rate": "On-time Start Rate",
|
||||||
"batches_started_on_time": "Batches started on time",
|
"batches_started_on_time": "Batches started on time",
|
||||||
@@ -562,7 +563,29 @@
|
|||||||
"view_vehicles": "View Vehicles",
|
"view_vehicles": "View Vehicles",
|
||||||
"live_tracking": "Live GPS Tracking",
|
"live_tracking": "Live GPS Tracking",
|
||||||
"real_time_gps_tracking": "Real-time GPS tracking of all vehicles",
|
"real_time_gps_tracking": "Real-time GPS tracking of all vehicles",
|
||||||
"open_tracking_map": "Open Tracking Map"
|
"open_tracking_map": "Open Tracking Map",
|
||||||
|
"no_outlets": "No outlets available",
|
||||||
|
"summary_view": "Summary View",
|
||||||
|
"detailed_view": "Detailed View",
|
||||||
|
"product_level_inventory": "Product-Level Inventory",
|
||||||
|
"back_to_summary": "Back to Summary",
|
||||||
|
"current_stock": "Current Stock",
|
||||||
|
"safety_stock": "Safety Stock",
|
||||||
|
"coverage_of_safety": "Coverage of Safety",
|
||||||
|
"risk_level": "Risk Level",
|
||||||
|
"stock_above_safety": "Stock Above Safety",
|
||||||
|
"yes": "Yes",
|
||||||
|
"no": "No",
|
||||||
|
"transfer_stock": "Transfer Stock",
|
||||||
|
"critical_outlets": "Critical Outlets",
|
||||||
|
"critical_outlets_description": "There are {count} outlets with critical inventory issues",
|
||||||
|
"prioritize_transfers": "Prioritize Transfers",
|
||||||
|
"low_coverage_recommendation": "Inventory coverage is low. Consider increasing stock levels.",
|
||||||
|
"good_coverage_recommendation": "Good inventory coverage - maintain current levels",
|
||||||
|
"fulfillment_excellence": "Fulfillment Excellence",
|
||||||
|
"high_fulfillment_congrats": "Congratulations! Your network has a {rate}% fulfillment rate",
|
||||||
|
"maintain_excellence": "Maintain Excellence",
|
||||||
|
"all_outlets_healthy": "All outlets are operating normally"
|
||||||
},
|
},
|
||||||
"ai_insights": {
|
"ai_insights": {
|
||||||
"title": "AI Insights",
|
"title": "AI Insights",
|
||||||
|
|||||||
@@ -83,6 +83,7 @@
|
|||||||
"status_critical": "Crítico",
|
"status_critical": "Crítico",
|
||||||
"status_warning": "Advertencia",
|
"status_warning": "Advertencia",
|
||||||
"status_normal": "Normal",
|
"status_normal": "Normal",
|
||||||
|
"no_equipment": "No hay equipos disponibles",
|
||||||
"efficiency_metrics": "Métricas de Eficiencia de Producción",
|
"efficiency_metrics": "Métricas de Eficiencia de Producción",
|
||||||
"on_time_start_rate": "Tasa de Inicio a Tiempo",
|
"on_time_start_rate": "Tasa de Inicio a Tiempo",
|
||||||
"batches_started_on_time": "Lotes iniciados a tiempo",
|
"batches_started_on_time": "Lotes iniciados a tiempo",
|
||||||
@@ -633,7 +634,29 @@
|
|||||||
"view_vehicles": "Ver Vehículos",
|
"view_vehicles": "Ver Vehículos",
|
||||||
"live_tracking": "Seguimiento GPS en Vivo",
|
"live_tracking": "Seguimiento GPS en Vivo",
|
||||||
"real_time_gps_tracking": "Seguimiento GPS en tiempo real de todos los vehículos",
|
"real_time_gps_tracking": "Seguimiento GPS en tiempo real de todos los vehículos",
|
||||||
"open_tracking_map": "Abrir Mapa de Seguimiento"
|
"open_tracking_map": "Abrir Mapa de Seguimiento",
|
||||||
|
"no_outlets": "No hay tiendas disponibles",
|
||||||
|
"summary_view": "Vista de Resumen",
|
||||||
|
"detailed_view": "Vista Detallada",
|
||||||
|
"product_level_inventory": "Inventario a Nivel de Producto",
|
||||||
|
"back_to_summary": "Volver al Resumen",
|
||||||
|
"current_stock": "Stock Actual",
|
||||||
|
"safety_stock": "Stock de Seguridad",
|
||||||
|
"coverage_of_safety": "Cobertura del Stock de Seguridad",
|
||||||
|
"risk_level": "Nivel de Riesgo",
|
||||||
|
"stock_above_safety": "Stock por Encima del de Seguridad",
|
||||||
|
"yes": "Sí",
|
||||||
|
"no": "No",
|
||||||
|
"transfer_stock": "Transferir Stock",
|
||||||
|
"critical_outlets": "Tiendas Críticas",
|
||||||
|
"critical_outlets_description": "Hay {count} tiendas con problemas críticos de inventario",
|
||||||
|
"prioritize_transfers": "Priorizar Transferencias",
|
||||||
|
"low_coverage_recommendation": "La cobertura de inventario es baja. Considere aumentar los niveles de stock.",
|
||||||
|
"good_coverage_recommendation": "Buena cobertura de inventario - mantenga los niveles actuales",
|
||||||
|
"fulfillment_excellence": "Excelencia en Cumplimiento",
|
||||||
|
"high_fulfillment_congrats": "¡Felicitaciones! Su red tiene una tasa de cumplimiento del {rate}%",
|
||||||
|
"maintain_excellence": "Mantener Excelencia",
|
||||||
|
"all_outlets_healthy": "Todas las tiendas operan con normalidad"
|
||||||
},
|
},
|
||||||
"ai_insights": {
|
"ai_insights": {
|
||||||
"title": "Insights de IA",
|
"title": "Insights de IA",
|
||||||
|
|||||||
@@ -134,7 +134,30 @@
|
|||||||
"completed": "OSATUTA",
|
"completed": "OSATUTA",
|
||||||
"in_progress": "MARTXAN",
|
"in_progress": "MARTXAN",
|
||||||
"pending": "ITXAROTEAN"
|
"pending": "ITXAROTEAN"
|
||||||
}
|
},
|
||||||
|
"status_critical": "Kritikoa",
|
||||||
|
"status_warning": "Abisua",
|
||||||
|
"status_normal": "Normala",
|
||||||
|
"no_equipment": "Ez dago ekipamendurik erabilgarri",
|
||||||
|
"efficiency_metrics": "Ekoizpen Eraginkortasunaren Metrikak",
|
||||||
|
"on_time_start_rate": "Denboraren Arabera Hasieraren Tasa",
|
||||||
|
"batches_started_on_time": "Denboraren arabera hasitako sortak",
|
||||||
|
"efficiency_rate": "Eraginkortasun Tasa",
|
||||||
|
"overall_efficiency": "Ekoizpen eraginkortasun orokorra",
|
||||||
|
"active_alerts": "Alerta Aktiboak",
|
||||||
|
"issues_require_attention": "Arreta behar duten arazoak",
|
||||||
|
"ai_prevented": "ADk Saihestutako Arazoak",
|
||||||
|
"problems_prevented": "ADk saihestutako arazoak",
|
||||||
|
"quick_actions": "Ekintza Azkarrak",
|
||||||
|
"create_batch": "Sortu Ekoizpen Sorta",
|
||||||
|
"create_batch_description": "Sortu ekoizpen sorta berria sarearentzat",
|
||||||
|
"maintenance": "Mantentzea",
|
||||||
|
"schedule_maintenance": "Programatu mantentzea ekoizpen ekipamendurako",
|
||||||
|
"manage_equipment": "Kudeatu Ekipamendua",
|
||||||
|
"quality_checks": "Kalitate Egiaztapenak",
|
||||||
|
"manage_quality": "Kudeatu kalitate kontrol prozesuak",
|
||||||
|
"quality_management": "Kalitate Kudeaketa",
|
||||||
|
"event_message": "Mezua"
|
||||||
},
|
},
|
||||||
"messages": {
|
"messages": {
|
||||||
"welcome": "Ongi etorri berriro",
|
"welcome": "Ongi etorri berriro",
|
||||||
@@ -390,7 +413,29 @@
|
|||||||
"in_transit": "Bidaiatzen",
|
"in_transit": "Bidaiatzen",
|
||||||
"delivered": "Entregatua",
|
"delivered": "Entregatua",
|
||||||
"failed": "Huts egin du",
|
"failed": "Huts egin du",
|
||||||
"distribution_routes": "Banaketa Ibilbideak"
|
"distribution_routes": "Banaketa Ibilbideak",
|
||||||
|
"no_outlets": "Ez dago dendarik erabilgarri",
|
||||||
|
"summary_view": "Laburpen Ikuspegia",
|
||||||
|
"detailed_view": "Ikuspegi Xehatua",
|
||||||
|
"product_level_inventory": "Produktu Mailako Inbentarioa",
|
||||||
|
"back_to_summary": "Bueltatu Laburpena-ra",
|
||||||
|
"current_stock": "Uneko Stock-a",
|
||||||
|
"safety_stock": "Segurtasun Stock-a",
|
||||||
|
"coverage_of_safety": "Segurtasun Stockaren Estaldura",
|
||||||
|
"risk_level": "Arrisku Maila",
|
||||||
|
"stock_above_safety": "Segurtasunaren Gainetik Stock-a",
|
||||||
|
"yes": "Bai",
|
||||||
|
"no": "Ez",
|
||||||
|
"transfer_stock": "Stock-a Transferitu",
|
||||||
|
"critical_outlets": "Denda Kritikoak",
|
||||||
|
"critical_outlets_description": "{count} denda daude inbentario arazo kritikoekin",
|
||||||
|
"prioritize_transfers": "Transferentziak Lehentasun",
|
||||||
|
"low_coverage_recommendation": "Inbentario estaldura baxua da. Kontutan izan stock mailak handitzea.",
|
||||||
|
"good_coverage_recommendation": "Inbentario estaldura ona - mantendu uneko mailak",
|
||||||
|
"fulfillment_excellence": "Betetze Bikaintasuna",
|
||||||
|
"high_fulfillment_congrats": "Zorionak! Zure sareak %{rate}ko betetze tasa du",
|
||||||
|
"maintain_excellence": "Mantendu Bikaintasuna",
|
||||||
|
"all_outlets_healthy": "Denda guztiak arrunta lanean"
|
||||||
},
|
},
|
||||||
"new_dashboard": {
|
"new_dashboard": {
|
||||||
"system_status": {
|
"system_status": {
|
||||||
|
|||||||
Reference in New Issue
Block a user