diff --git a/src/components/analytics/OEECharts.tsx b/src/components/analytics/OEECharts.tsx index cc8bf2c3..5b96c5ac 100644 --- a/src/components/analytics/OEECharts.tsx +++ b/src/components/analytics/OEECharts.tsx @@ -13,30 +13,52 @@ import { Cell, } from "recharts"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useOEEMetrics } from "@/hooks/useOEEMetrics"; +import { useTranslation } from "react-i18next"; +import { Loader2 } from "lucide-react"; + +// Centralized styling +const TOOLTIP_STYLE = { + contentStyle: { + backgroundColor: "hsl(var(--popover))", + borderColor: "hsl(var(--border))", + color: "hsl(var(--popover-foreground))", + borderRadius: "8px", + }, + cursor: { fill: "hsl(var(--muted)/0.2)" }, +}; + +const AXIS_STYLE = { + stroke: "hsl(var(--muted-foreground))", +}; const OEECharts = () => { - // Mock Data - const oeeData = [ - { name: "Availability", value: 85, fill: "hsl(var(--brand-primary))" }, - { name: "Performance", value: 92, fill: "hsl(var(--brand-accent))" }, - { name: "Quality", value: 98, fill: "hsl(var(--color-success))" }, - ]; + const { t } = useTranslation(); + const { data: metrics, isLoading } = useOEEMetrics(30); - const machineStateData = [ - { name: "Running", value: 65, color: "hsl(var(--color-success))" }, - { name: "Idle", value: 20, color: "hsl(var(--color-warning))" }, - { name: "Down", value: 10, color: "hsl(var(--color-error))" }, - { name: "Maintenance", value: 5, color: "hsl(var(--neutral-400))" }, - ]; + if (isLoading) { + return ( +
+ +
+ ); + } - const trendData = [ - { name: "Mon", oee: 82 }, - { name: "Tue", oee: 84 }, - { name: "Wed", oee: 88 }, - { name: "Thu", oee: 85 }, - { name: "Fri", oee: 90 }, - { name: "Sat", oee: 87 }, - { name: "Sun", oee: 89 }, + if (!metrics) { + return ( + + +

{t("analytics.noOEEData")}

+
+
+ ); + } + + // Transform OEE breakdown data + const oeeData = [ + { name: t("oee.availability"), value: metrics.availability, fill: "hsl(var(--brand-primary))" }, + { name: t("oee.performance"), value: metrics.performance, fill: "hsl(var(--brand-accent))" }, + { name: t("oee.quality"), value: metrics.quality, fill: "hsl(var(--color-success))" }, ]; return ( @@ -44,22 +66,41 @@ const OEECharts = () => { {/* OEE Breakdown */} - OEE Breakdown + + {t("oee.breakdown")} + + {metrics.oee}% + +
- - - - + + + + [`${value}%`, ""]} /> @@ -68,38 +109,40 @@ const OEECharts = () => { - {/* Machine States */} + {/* Operation States */} - Machine States + {t("oee.operationStates")}
- - - - {machineStateData.map((entry, index) => ( - - ))} - - - - - + {metrics.stateBreakdown.length > 0 ? ( + + + + {metrics.stateBreakdown.map((entry, index) => ( + + ))} + + [`${value}%`, ""]} + /> + + + + ) : ( +

{t("analytics.noData")}

+ )}
@@ -107,31 +150,93 @@ const OEECharts = () => { {/* OEE Trend */} - OEE Trend (Last 7 Days) + {t("oee.trend")}
- - - - - - - - - + {metrics.trend.length > 0 ? ( + + + + + + [ + `${value}%`, + name === "oee" ? "OEE" : name + ]} + /> + + + + ) : ( +
+

{t("analytics.noTrendData")}

+
+ )}
+ + {/* OEE by Cell */} + {metrics.byCell.length > 0 && ( + + + {t("oee.byCell")} + + +
+ + + + + + [`${value}%`, ""]} + /> + + + + +
+
+
+ )}
); }; +OEECharts.displayName = "OEECharts"; + export { OEECharts }; diff --git a/src/components/analytics/ReliabilityCharts.tsx b/src/components/analytics/ReliabilityCharts.tsx index 6796f65d..62e1f65a 100644 --- a/src/components/analytics/ReliabilityCharts.tsx +++ b/src/components/analytics/ReliabilityCharts.tsx @@ -10,103 +10,224 @@ import { ResponsiveContainer, AreaChart, Area, + BarChart, + Bar, } from "recharts"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useReliabilityMetrics } from "@/hooks/useReliabilityMetrics"; +import { useTranslation } from "react-i18next"; +import { Loader2 } from "lucide-react"; + +// Centralized styling +const TOOLTIP_STYLE = { + contentStyle: { + backgroundColor: "hsl(var(--popover))", + borderColor: "hsl(var(--border))", + color: "hsl(var(--popover-foreground))", + borderRadius: "8px", + }, +}; + +const AXIS_STYLE = { + stroke: "hsl(var(--muted-foreground))", +}; const ReliabilityCharts = () => { - // Mock Data - const reliabilityData = [ - { date: "Week 1", onTime: 88, late: 12 }, - { date: "Week 2", onTime: 92, late: 8 }, - { date: "Week 3", onTime: 85, late: 15 }, - { date: "Week 4", onTime: 95, late: 5 }, - { date: "Week 5", onTime: 90, late: 10 }, - ]; + const { t } = useTranslation(); + const { data: metrics, isLoading } = useReliabilityMetrics(30); - const startDelayData = [ - { date: "Mon", delay: 15 }, // minutes - { date: "Tue", delay: 5 }, - { date: "Wed", delay: 25 }, - { date: "Thu", delay: 10 }, - { date: "Fri", delay: 8 }, - ]; + if (isLoading) { + return ( +
+ +
+ ); + } + + if (!metrics) { + return ( + + +

{t("analytics.noReliabilityData")}

+
+
+ ); + } return (
- {/* On-Time Start Performance */} + {/* Summary Stats */} - On-Time Start Performance + {t("reliability.summary")} + + +
+
+
{t("reliability.totalOperations")}
+
{metrics.totalOperations}
+
+
+
{t("reliability.onTime")}
+
+ {metrics.onTimeOperations} + ({metrics.onTimePercentage}%) +
+
+
+
{t("reliability.late")}
+
+ {metrics.lateOperations} + ({metrics.latePercentage}%) +
+
+
+
{t("reliability.avgDelay")}
+
+ {metrics.avgDelayMinutes} + {t("reliability.minutes")} +
+
+
+
+
+ + {/* On-Time Performance Trend */} + + + {t("reliability.onTimePerformance")}
- - - - - - - - - - - - - - - - + {metrics.weeklyTrend.length > 0 ? ( + + + + + + + + + + + + [`${value}%`, ""]} + /> + + + + + ) : ( +
+

{t("analytics.noTrendData")}

+
+ )}
- {/* Average Start Delay */} + {/* Average Delay Trend */} - Average Start Delay (Minutes) + {t("reliability.avgDelayTrend")}
- - - - - - - - - + {metrics.delayTrend.length > 0 ? ( + + + + + + [`${value} min`, ""]} + /> + + + + ) : ( +
+

{t("analytics.noTrendData")}

+
+ )}
+ + {/* Reliability by Cell */} + {metrics.byCell.length > 0 && ( + + + {t("reliability.byCell")} + + +
+ + + + + + [`${value}%`, ""]} + /> + + + + +
+
+
+ )}
); }; +ReliabilityCharts.displayName = "ReliabilityCharts"; + export { ReliabilityCharts }; diff --git a/src/hooks/useOEEMetrics.ts b/src/hooks/useOEEMetrics.ts new file mode 100644 index 00000000..69289044 --- /dev/null +++ b/src/hooks/useOEEMetrics.ts @@ -0,0 +1,231 @@ +import { useQuery } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/contexts/AuthContext"; +import { subDays, format, eachDayOfInterval } from "date-fns"; + +export interface OEEMetrics { + // OEE Components + availability: number; + performance: number; + quality: number; + oee: number; + + // Machine/Cell states based on operations + stateBreakdown: { + name: string; + value: number; + color: string; + }[]; + + // OEE trend over time + trend: { + date: string; + oee: number; + availability: number; + performance: number; + quality: number; + }[]; + + // OEE by cell + byCell: { + cellName: string; + oee: number; + availability: number; + performance: number; + quality: number; + }[]; +} + +export function useOEEMetrics(dateRange: number = 30) { + const { profile } = useAuth(); + + return useQuery({ + queryKey: ["oee-metrics", profile?.tenant_id, dateRange], + queryFn: async (): Promise => { + if (!profile?.tenant_id) { + throw new Error("No tenant ID"); + } + + const startDate = subDays(new Date(), dateRange).toISOString(); + const now = new Date(); + + // Fetch operations with cell info + const { data: operations, error: opsError } = await supabase + .from("operations") + .select(` + id, + actual_time, + estimated_time, + setup_time, + wait_time, + status, + completed_at, + cell_id, + cells(name, capacity_hours_per_day) + `) + .eq("tenant_id", profile.tenant_id) + .gte("updated_at", startDate); + + if (opsError) throw opsError; + + // Fetch quality data from operation_quantities + const { data: quantities, error: quantitiesError } = await supabase + .from("operation_quantities") + .select(` + quantity_produced, + quantity_good, + quantity_scrap, + operation_id, + recorded_at + `) + .eq("tenant_id", profile.tenant_id) + .gte("recorded_at", startDate); + + if (quantitiesError) throw quantitiesError; + + // Calculate overall metrics + const completedOps = operations?.filter(o => o.status === "completed") || []; + + // Availability = actual production time / scheduled time + const totalActualTime = completedOps.reduce((sum, o) => sum + (o.actual_time || 0), 0); + const totalEstimatedTime = completedOps.reduce((sum, o) => sum + (o.estimated_time || 0), 0); + const totalWaitTime = operations?.reduce((sum, o) => sum + (o.wait_time || 0), 0) || 0; + const totalSetupTime = operations?.reduce((sum, o) => sum + (o.setup_time || 0), 0) || 0; + + // Calculate availability (productive time / available time) + const plannedTime = totalEstimatedTime + totalSetupTime; + const availability = plannedTime > 0 + ? Math.min(100, ((plannedTime - totalWaitTime) / plannedTime) * 100) + : 100; + + // Performance = actual output rate / ideal output rate + const performance = totalActualTime > 0 && totalEstimatedTime > 0 + ? Math.min(100, (totalEstimatedTime / totalActualTime) * 100) + : 100; + + // Quality from quantities + const totalProduced = quantities?.reduce((sum, q) => sum + (q.quantity_produced || 0), 0) || 0; + const totalGood = quantities?.reduce((sum, q) => sum + (q.quantity_good || 0), 0) || 0; + const quality = totalProduced > 0 ? (totalGood / totalProduced) * 100 : 100; + + // Overall OEE + const oee = (availability * performance * quality) / 10000; + + // State breakdown based on operation status + const statusCounts = { + completed: operations?.filter(o => o.status === "completed").length || 0, + in_progress: operations?.filter(o => o.status === "in_progress").length || 0, + pending: operations?.filter(o => o.status === "pending" || o.status === "not_started").length || 0, + on_hold: operations?.filter(o => o.status === "on_hold").length || 0, + }; + const totalOps = Object.values(statusCounts).reduce((a, b) => a + b, 0) || 1; + + const stateBreakdown = [ + { name: "Completed", value: Math.round((statusCounts.completed / totalOps) * 100), color: "hsl(var(--color-success))" }, + { name: "In Progress", value: Math.round((statusCounts.in_progress / totalOps) * 100), color: "hsl(var(--brand-primary))" }, + { name: "Pending", value: Math.round((statusCounts.pending / totalOps) * 100), color: "hsl(var(--color-warning))" }, + { name: "On Hold", value: Math.round((statusCounts.on_hold / totalOps) * 100), color: "hsl(var(--neutral-400))" }, + ].filter(s => s.value > 0); + + // Trend calculation - group by day + const dateInterval = eachDayOfInterval({ start: subDays(now, Math.min(dateRange, 14)), end: now }); + const trendSampleInterval = Math.max(1, Math.floor(dateInterval.length / 7)); + + const trend = dateInterval + .filter((_, i) => i % trendSampleInterval === 0) + .map(date => { + const dayStr = format(date, "yyyy-MM-dd"); + const dayOps = completedOps.filter(o => + o.completed_at && format(new Date(o.completed_at), "yyyy-MM-dd") === dayStr + ); + const dayQuantities = quantities?.filter(q => + q.recorded_at && format(new Date(q.recorded_at), "yyyy-MM-dd") === dayStr + ) || []; + + const dayEstTime = dayOps.reduce((sum, o) => sum + (o.estimated_time || 0), 0); + const dayActTime = dayOps.reduce((sum, o) => sum + (o.actual_time || 0), 0); + const dayProduced = dayQuantities.reduce((sum, q) => sum + (q.quantity_produced || 0), 0); + const dayGood = dayQuantities.reduce((sum, q) => sum + (q.quantity_good || 0), 0); + + const dayAvail = dayOps.length > 0 ? availability : 100; + const dayPerf = dayActTime > 0 ? Math.min(100, (dayEstTime / dayActTime) * 100) : performance; + const dayQual = dayProduced > 0 ? (dayGood / dayProduced) * 100 : quality; + const dayOee = (dayAvail * dayPerf * dayQual) / 10000; + + return { + date: format(date, "MMM d"), + oee: Number(dayOee.toFixed(1)), + availability: Number(dayAvail.toFixed(1)), + performance: Number(dayPerf.toFixed(1)), + quality: Number(dayQual.toFixed(1)), + }; + }); + + // OEE by cell + const cellMap = new Map(); + + operations?.forEach(op => { + if (op.cells?.name) { + const cellName = op.cells.name; + const current = cellMap.get(cellName) || { estTime: 0, actTime: 0, produced: 0, good: 0, waitTime: 0 }; + cellMap.set(cellName, { + estTime: current.estTime + (op.estimated_time || 0), + actTime: current.actTime + (op.actual_time || 0), + produced: current.produced, + good: current.good, + waitTime: current.waitTime + (op.wait_time || 0), + }); + } + }); + + // Add quality data to cells + quantities?.forEach(q => { + const op = operations?.find(o => o.id === q.operation_id); + if (op?.cells?.name) { + const current = cellMap.get(op.cells.name); + if (current) { + current.produced += q.quantity_produced || 0; + current.good += q.quantity_good || 0; + } + } + }); + + const byCell = Array.from(cellMap.entries()).map(([cellName, data]) => { + const cellAvail = data.estTime > 0 + ? Math.min(100, ((data.estTime - data.waitTime) / data.estTime) * 100) + : 100; + const cellPerf = data.actTime > 0 && data.estTime > 0 + ? Math.min(100, (data.estTime / data.actTime) * 100) + : 100; + const cellQual = data.produced > 0 ? (data.good / data.produced) * 100 : 100; + const cellOee = (cellAvail * cellPerf * cellQual) / 10000; + + return { + cellName, + oee: Number(cellOee.toFixed(1)), + availability: Number(cellAvail.toFixed(1)), + performance: Number(cellPerf.toFixed(1)), + quality: Number(cellQual.toFixed(1)), + }; + }).sort((a, b) => b.oee - a.oee).slice(0, 8); + + return { + availability: Number(availability.toFixed(1)), + performance: Number(performance.toFixed(1)), + quality: Number(quality.toFixed(1)), + oee: Number(oee.toFixed(1)), + stateBreakdown, + trend, + byCell, + }; + }, + enabled: !!profile?.tenant_id, + staleTime: 60000, + }); +} diff --git a/src/hooks/useReliabilityMetrics.ts b/src/hooks/useReliabilityMetrics.ts new file mode 100644 index 00000000..fa5a47fc --- /dev/null +++ b/src/hooks/useReliabilityMetrics.ts @@ -0,0 +1,206 @@ +import { useQuery } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/contexts/AuthContext"; +import { subDays, format, eachDayOfInterval, differenceInMinutes, startOfWeek } from "date-fns"; + +export interface ReliabilityMetrics { + // Overall on-time performance + onTimePercentage: number; + latePercentage: number; + + // Weekly trend + weeklyTrend: { + date: string; + onTime: number; + late: number; + }[]; + + // Average delay in minutes + avgDelayMinutes: number; + + // Daily delay trend + delayTrend: { + date: string; + delay: number; + }[]; + + // On-time by cell + byCell: { + cellName: string; + onTimePercentage: number; + avgDelay: number; + totalOperations: number; + }[]; + + // Summary stats + totalOperations: number; + onTimeOperations: number; + lateOperations: number; +} + +export function useReliabilityMetrics(dateRange: number = 30) { + const { profile } = useAuth(); + + return useQuery({ + queryKey: ["reliability-metrics", profile?.tenant_id, dateRange], + queryFn: async (): Promise => { + if (!profile?.tenant_id) { + throw new Error("No tenant ID"); + } + + const startDate = subDays(new Date(), dateRange).toISOString(); + const now = new Date(); + + // Fetch operations with planned vs actual dates + const { data: operations, error: opsError } = await supabase + .from("operations") + .select(` + id, + planned_start, + planned_end, + completed_at, + status, + cell_id, + cells(name), + created_at, + updated_at + `) + .eq("tenant_id", profile.tenant_id) + .eq("status", "completed") + .gte("completed_at", startDate); + + if (opsError) throw opsError; + + const completedOps = operations || []; + + // Calculate on-time vs late + let onTimeCount = 0; + let lateCount = 0; + let totalDelayMinutes = 0; + let opsWithDelay = 0; + + const delaysByDay = new Map(); + const onTimeByWeek = new Map(); + const cellStats = new Map(); + + completedOps.forEach(op => { + const completedAt = op.completed_at ? new Date(op.completed_at) : null; + const plannedEnd = op.planned_end ? new Date(op.planned_end) : null; + + // Determine if on-time + let isOnTime = true; + let delayMinutes = 0; + + if (completedAt && plannedEnd) { + isOnTime = completedAt <= plannedEnd; + if (!isOnTime) { + delayMinutes = differenceInMinutes(completedAt, plannedEnd); + totalDelayMinutes += delayMinutes; + opsWithDelay++; + } + } + + if (isOnTime) { + onTimeCount++; + } else { + lateCount++; + } + + // Track by day for delay trend + if (completedAt) { + const dayStr = format(completedAt, "yyyy-MM-dd"); + const existing = delaysByDay.get(dayStr) || { total: 0, count: 0 }; + delaysByDay.set(dayStr, { + total: existing.total + delayMinutes, + count: existing.count + 1, + }); + } + + // Track by week for on-time trend + if (completedAt) { + const weekStr = format(startOfWeek(completedAt), "MMM d"); + const existing = onTimeByWeek.get(weekStr) || { onTime: 0, late: 0 }; + if (isOnTime) { + existing.onTime++; + } else { + existing.late++; + } + onTimeByWeek.set(weekStr, existing); + } + + // Track by cell + if (op.cells?.name) { + const cellName = op.cells.name; + const existing = cellStats.get(cellName) || { onTime: 0, late: 0, totalDelay: 0 }; + if (isOnTime) { + existing.onTime++; + } else { + existing.late++; + existing.totalDelay += delayMinutes; + } + cellStats.set(cellName, existing); + } + }); + + const totalOps = completedOps.length || 1; + const onTimePercentage = (onTimeCount / totalOps) * 100; + const latePercentage = (lateCount / totalOps) * 100; + const avgDelayMinutes = opsWithDelay > 0 ? totalDelayMinutes / opsWithDelay : 0; + + // Build weekly trend (last 5 weeks) + const weeklyTrend = Array.from(onTimeByWeek.entries()) + .map(([date, data]) => ({ + date, + onTime: Math.round((data.onTime / (data.onTime + data.late)) * 100), + late: Math.round((data.late / (data.onTime + data.late)) * 100), + })) + .slice(-5); + + // Build daily delay trend (sample to ~7 points) + const dateInterval = eachDayOfInterval({ start: subDays(now, Math.min(dateRange, 14)), end: now }); + const trendSampleInterval = Math.max(1, Math.floor(dateInterval.length / 7)); + + const delayTrend = dateInterval + .filter((_, i) => i % trendSampleInterval === 0) + .map(date => { + const dayStr = format(date, "yyyy-MM-dd"); + const dayData = delaysByDay.get(dayStr); + const avgDelay = dayData && dayData.count > 0 + ? dayData.total / dayData.count + : 0; + return { + date: format(date, "EEE"), + delay: Math.round(avgDelay), + }; + }); + + // Build by cell stats + const byCell = Array.from(cellStats.entries()) + .map(([cellName, data]) => { + const total = data.onTime + data.late; + return { + cellName, + onTimePercentage: total > 0 ? Math.round((data.onTime / total) * 100) : 100, + avgDelay: data.late > 0 ? Math.round(data.totalDelay / data.late) : 0, + totalOperations: total, + }; + }) + .sort((a, b) => b.totalOperations - a.totalOperations) + .slice(0, 8); + + return { + onTimePercentage: Number(onTimePercentage.toFixed(1)), + latePercentage: Number(latePercentage.toFixed(1)), + weeklyTrend, + avgDelayMinutes: Math.round(avgDelayMinutes), + delayTrend, + byCell, + totalOperations: completedOps.length, + onTimeOperations: onTimeCount, + lateOperations: lateCount, + }; + }, + enabled: !!profile?.tenant_id, + staleTime: 60000, + }); +} diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 6dbfa7af..01b11cbd 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -1737,7 +1737,33 @@ "jobsSubtitle": "Verfolgen Sie Auftragsstatus, Fristen und Qualitätsmetriken", "qualityTitle": "Qualitätsanalysen", "qualitySubtitle": "Überwachen Sie Ausbeute, Ausschuss und Problemmetriken", - "noQualityData": "Noch keine Qualitätsdaten verfügbar. Beginnen Sie mit der Produktionserfassung, um Metriken zu sehen." + "noQualityData": "Noch keine Qualitätsdaten verfügbar. Beginnen Sie mit der Produktionserfassung, um Metriken zu sehen.", + "noOEEData": "Noch keine OEE-Daten verfügbar. Schließen Sie Arbeitsgänge ab, um Effizienzmetriken zu sehen.", + "noReliabilityData": "Noch keine Zuverlässigkeitsdaten verfügbar. Schließen Sie Arbeitsgänge mit geplanten Terminen ab, um Metriken zu sehen.", + "noData": "Keine Daten verfügbar", + "noTrendData": "Keine Trenddaten für diesen Zeitraum verfügbar" + }, + "oee": { + "breakdown": "OEE-Aufschlüsselung", + "availability": "Verfügbarkeit", + "performance": "Leistung", + "quality": "Qualität", + "operationStates": "Arbeitsgangstatus", + "trend": "OEE-Trend", + "byCell": "OEE nach Zelle" + }, + "reliability": { + "summary": "Zuverlässigkeitsübersicht", + "totalOperations": "Gesamte Arbeitsgänge", + "onTime": "Pünktlich", + "late": "Verspätet", + "avgDelay": "Durchschn. Verzögerung", + "minutes": "Min", + "onTimePerformance": "Pünktlichkeitsleistung", + "onTimePercent": "Pünktlich %", + "avgDelayTrend": "Durchschnittlicher Verzögerungstrend", + "delayMin": "Verzögerung (Min)", + "byCell": "Zuverlässigkeit nach Zelle" }, "sessionTracking": { "currentlyTracking": "Aktuelle Zeiterfassung", diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index b3708a9b..976a6dbf 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -1979,7 +1979,33 @@ "jobsSubtitle": "Track job status, deadlines, and quality metrics", "qualityTitle": "Quality Analytics", "qualitySubtitle": "Monitor yield rates, scrap, and issue metrics", - "noQualityData": "No quality data available yet. Start recording production to see metrics." + "noQualityData": "No quality data available yet. Start recording production to see metrics.", + "noOEEData": "No OEE data available yet. Complete operations to see efficiency metrics.", + "noReliabilityData": "No reliability data available yet. Complete operations with planned dates to see metrics.", + "noData": "No data available", + "noTrendData": "No trend data available for this period" + }, + "oee": { + "breakdown": "OEE Breakdown", + "availability": "Availability", + "performance": "Performance", + "quality": "Quality", + "operationStates": "Operation States", + "trend": "OEE Trend", + "byCell": "OEE by Cell" + }, + "reliability": { + "summary": "Reliability Summary", + "totalOperations": "Total Operations", + "onTime": "On Time", + "late": "Late", + "avgDelay": "Avg Delay", + "minutes": "min", + "onTimePerformance": "On-Time Performance", + "onTimePercent": "On-Time %", + "avgDelayTrend": "Average Delay Trend", + "delayMin": "Delay (min)", + "byCell": "Reliability by Cell" }, "capacity": { "title": "Capacity Matrix", diff --git a/src/i18n/locales/nl/translation.json b/src/i18n/locales/nl/translation.json index 3d34fdbf..a690689b 100644 --- a/src/i18n/locales/nl/translation.json +++ b/src/i18n/locales/nl/translation.json @@ -2007,7 +2007,33 @@ "jobsSubtitle": "Volg orderstatus, deadlines en kwaliteitsmetrieken", "qualityTitle": "Kwaliteitsanalyses", "qualitySubtitle": "Monitor opbrengsten, afval en probleemmetrieken", - "noQualityData": "Nog geen kwaliteitsgegevens beschikbaar. Begin met productieregistratie om metrieken te zien." + "noQualityData": "Nog geen kwaliteitsgegevens beschikbaar. Begin met productieregistratie om metrieken te zien.", + "noOEEData": "Nog geen OEE-gegevens beschikbaar. Voltooi bewerkingen om efficiëntiemetrieken te zien.", + "noReliabilityData": "Nog geen betrouwbaarheidsgegevens beschikbaar. Voltooi bewerkingen met geplande datums om metrieken te zien.", + "noData": "Geen gegevens beschikbaar", + "noTrendData": "Geen trendgegevens beschikbaar voor deze periode" + }, + "oee": { + "breakdown": "OEE Uitsplitsing", + "availability": "Beschikbaarheid", + "performance": "Prestaties", + "quality": "Kwaliteit", + "operationStates": "Bewerkingsstatussen", + "trend": "OEE Trend", + "byCell": "OEE per Cel" + }, + "reliability": { + "summary": "Betrouwbaarheidsoverzicht", + "totalOperations": "Totaal Bewerkingen", + "onTime": "Op Tijd", + "late": "Te Laat", + "avgDelay": "Gem. Vertraging", + "minutes": "min", + "onTimePerformance": "Op-Tijd Prestaties", + "onTimePercent": "Op-Tijd %", + "avgDelayTrend": "Gemiddelde Vertragingstrend", + "delayMin": "Vertraging (min)", + "byCell": "Betrouwbaarheid per Cel" }, "sessionTracking": { "currentlyTracking": "Momenteel aan het bijhouden",