Просмотр исходного кода

feat: translate Dashboard components (Header, KPICards, RecentCallsTable) #69

Claude 5 месяцев назад
Родитель
Сommit
d41fbf4013

+ 14 - 12
shopcall.ai-main/src/components/DashboardHeader.tsx

@@ -9,8 +9,10 @@ import { useState } from "react";
 import { format } from "date-fns";
 import { format } from "date-fns";
 import { DateRange } from "react-day-picker";
 import { DateRange } from "react-day-picker";
 import { cn } from "@/lib/utils";
 import { cn } from "@/lib/utils";
+import { useTranslation } from "react-i18next";
 
 
 export function DashboardHeader() {
 export function DashboardHeader() {
+  const { t } = useTranslation();
   const [dateRange, setDateRange] = useState<DateRange | undefined>();
   const [dateRange, setDateRange] = useState<DateRange | undefined>();
   const [quickSelect, setQuickSelect] = useState<string>("1");
   const [quickSelect, setQuickSelect] = useState<string>("1");
 
 
@@ -33,29 +35,29 @@ export function DashboardHeader() {
     <div className="border-b border-slate-800 bg-slate-900">
     <div className="border-b border-slate-800 bg-slate-900">
       <div className="flex items-center justify-between p-6">
       <div className="flex items-center justify-between p-6">
         <div>
         <div>
-          <h1 className="text-2xl font-semibold text-white">AI Call Dashboard</h1>
-          <p className="text-slate-400">Your AI calling insights for June 10, 2025</p>
+          <h1 className="text-2xl font-semibold text-white">{t('dashboard.title')}</h1>
+          <p className="text-slate-400">{t('dashboard.subtitle')} for {format(new Date(), 'MMMM dd, yyyy')}</p>
         </div>
         </div>
         
         
         <div className="flex items-center gap-4">
         <div className="flex items-center gap-4">
           <div className="flex items-center gap-2 bg-slate-800 rounded-lg p-1">
           <div className="flex items-center gap-2 bg-slate-800 rounded-lg p-1">
-            <ToggleGroup 
-              type="single" 
-              value={quickSelect} 
+            <ToggleGroup
+              type="single"
+              value={quickSelect}
               onValueChange={handleQuickSelect}
               onValueChange={handleQuickSelect}
               className="gap-0"
               className="gap-0"
             >
             >
               <ToggleGroupItem value="1" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
               <ToggleGroupItem value="1" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
-                24h
+                {t('dashboard.quickFilters.24h')}
               </ToggleGroupItem>
               </ToggleGroupItem>
               <ToggleGroupItem value="7" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
               <ToggleGroupItem value="7" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
-                7d
+                {t('dashboard.quickFilters.7d')}
               </ToggleGroupItem>
               </ToggleGroupItem>
               <ToggleGroupItem value="14" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
               <ToggleGroupItem value="14" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
-                14d
+                {t('dashboard.quickFilters.14d')}
               </ToggleGroupItem>
               </ToggleGroupItem>
               <ToggleGroupItem value="30" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
               <ToggleGroupItem value="30" className="text-xs px-3 py-1 data-[state=on]:bg-cyan-500 data-[state=on]:text-white">
-                30d
+                {t('dashboard.quickFilters.30d')}
               </ToggleGroupItem>
               </ToggleGroupItem>
             </ToggleGroup>
             </ToggleGroup>
             
             
@@ -80,7 +82,7 @@ export function DashboardHeader() {
                       format(dateRange.from, "MMM dd, yyyy")
                       format(dateRange.from, "MMM dd, yyyy")
                     )
                     )
                   ) : (
                   ) : (
-                    <span>Custom range</span>
+                    <span>{t('dashboard.quickFilters.customRange')}</span>
                   )}
                   )}
                 </Button>
                 </Button>
               </PopoverTrigger>
               </PopoverTrigger>
@@ -97,11 +99,11 @@ export function DashboardHeader() {
               </PopoverContent>
               </PopoverContent>
             </Popover>
             </Popover>
           </div>
           </div>
-          
+
           <div className="relative">
           <div className="relative">
             <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
             <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-slate-400 w-4 h-4" />
             <Input
             <Input
-              placeholder="Search calls..."
+              placeholder={t('dashboard.search')}
               className="pl-10 bg-slate-800 border-slate-700 text-white placeholder-slate-400 w-64"
               className="pl-10 bg-slate-800 border-slate-700 text-white placeholder-slate-400 w-64"
             />
             />
           </div>
           </div>

+ 15 - 13
shopcall.ai-main/src/components/KPICards.tsx

@@ -1,8 +1,10 @@
 import { Card } from "@/components/ui/card";
 import { Card } from "@/components/ui/card";
 import { Phone, CheckCircle, Clock, TrendingUp, DollarSign, UserX, Loader2 } from "lucide-react";
 import { Phone, CheckCircle, Clock, TrendingUp, DollarSign, UserX, Loader2 } from "lucide-react";
 import { useDashboard } from "./context/DashboardContext";
 import { useDashboard } from "./context/DashboardContext";
+import { useTranslation } from "react-i18next";
 
 
 export function KPICards() {
 export function KPICards() {
+  const { t } = useTranslation();
   const { stats, loading, error } = useDashboard();
   const { stats, loading, error } = useDashboard();
 
 
   if (loading) {
   if (loading) {
@@ -24,7 +26,7 @@ export function KPICards() {
       <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
       <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
         <Card className="bg-slate-800 border-slate-700 p-6 col-span-full">
         <Card className="bg-slate-800 border-slate-700 p-6 col-span-full">
           <div className="text-center text-slate-400">
           <div className="text-center text-slate-400">
-            {error || 'No data available'}
+            {error || t('dashboard.kpi.noData')}
           </div>
           </div>
         </Card>
         </Card>
       </div>
       </div>
@@ -33,56 +35,56 @@ export function KPICards() {
 
 
   const kpiData = [
   const kpiData = [
     {
     {
-      title: "Total Calls",
+      title: t('dashboard.kpi.totalCalls'),
       value: stats.totalCalls.value.toString(),
       value: stats.totalCalls.value.toString(),
       change: stats.totalCalls.change,
       change: stats.totalCalls.change,
       changeType: stats.totalCalls.changeType,
       changeType: stats.totalCalls.changeType,
-      subtitle: "vs yesterday",
+      subtitle: t('dashboard.kpi.vsYesterday'),
       icon: Phone,
       icon: Phone,
       iconColor: "text-blue-500",
       iconColor: "text-blue-500",
     },
     },
     {
     {
-      title: "Resolved Calls",
+      title: t('dashboard.kpi.resolvedCalls'),
       value: stats.resolvedCalls.value.toString(),
       value: stats.resolvedCalls.value.toString(),
       change: stats.resolvedCalls.change,
       change: stats.resolvedCalls.change,
       changeType: stats.resolvedCalls.changeType,
       changeType: stats.resolvedCalls.changeType,
-      subtitle: "vs yesterday",
+      subtitle: t('dashboard.kpi.vsYesterday'),
       icon: CheckCircle,
       icon: CheckCircle,
       iconColor: "text-green-500",
       iconColor: "text-green-500",
     },
     },
     {
     {
-      title: "Avg Call Duration",
+      title: t('dashboard.kpi.avgCallDuration'),
       value: stats.avgDuration.formatted,
       value: stats.avgDuration.formatted,
       change: stats.avgDuration.change,
       change: stats.avgDuration.change,
       changeType: stats.avgDuration.changeType,
       changeType: stats.avgDuration.changeType,
-      subtitle: "vs yesterday",
+      subtitle: t('dashboard.kpi.vsYesterday'),
       icon: Clock,
       icon: Clock,
       iconColor: "text-orange-500",
       iconColor: "text-orange-500",
     },
     },
     {
     {
-      title: "Total Cost",
+      title: t('dashboard.kpi.totalCost'),
       value: stats.totalCost.formatted,
       value: stats.totalCost.formatted,
       change: stats.totalCost.change,
       change: stats.totalCost.change,
       changeType: stats.totalCost.changeType,
       changeType: stats.totalCost.changeType,
-      subtitle: "vs yesterday",
+      subtitle: t('dashboard.kpi.vsYesterday'),
       icon: DollarSign,
       icon: DollarSign,
       iconColor: "text-green-500",
       iconColor: "text-green-500",
     },
     },
     {
     {
-      title: "Time Saved",
+      title: t('dashboard.kpi.timeSaved'),
       value: stats.timeSaved.formatted,
       value: stats.timeSaved.formatted,
       change: stats.timeSaved.change,
       change: stats.timeSaved.change,
       changeType: stats.timeSaved.changeType,
       changeType: stats.timeSaved.changeType,
-      subtitle: "vs yesterday",
+      subtitle: t('dashboard.kpi.vsYesterday'),
       icon: TrendingUp,
       icon: TrendingUp,
       iconColor: "text-purple-500",
       iconColor: "text-purple-500",
     },
     },
     {
     {
-      title: "Saved on Human Costs",
+      title: t('dashboard.kpi.savedOnHumanCosts'),
       value: stats.humanCostSaved.formatted,
       value: stats.humanCostSaved.formatted,
       change: stats.humanCostSaved.change,
       change: stats.humanCostSaved.change,
       changeType: stats.humanCostSaved.changeType,
       changeType: stats.humanCostSaved.changeType,
-      subtitle: "vs yesterday",
+      subtitle: t('dashboard.kpi.vsYesterday'),
       icon: UserX,
       icon: UserX,
       iconColor: "text-green-500",
       iconColor: "text-green-500",
     },
     },

+ 13 - 11
shopcall.ai-main/src/components/RecentCallsTable.tsx

@@ -4,6 +4,7 @@ import { Play, MoreHorizontal, Loader2 } from "lucide-react";
 import { useState, useEffect } from "react";
 import { useState, useEffect } from "react";
 import { CallDetailsModal } from "./CallDetailsModal";
 import { CallDetailsModal } from "./CallDetailsModal";
 import { API_URL } from "@/lib/config";
 import { API_URL } from "@/lib/config";
+import { useTranslation } from "react-i18next";
 
 
 interface CallLog {
 interface CallLog {
   time: string;
   time: string;
@@ -31,6 +32,7 @@ const getSentimentEmoji = (sentiment: string) => {
 };
 };
 
 
 export function RecentCallsTable() {
 export function RecentCallsTable() {
+  const { t } = useTranslation();
   const [recentCalls, setRecentCalls] = useState<CallLog[]>([]);
   const [recentCalls, setRecentCalls] = useState<CallLog[]>([]);
   const [loading, setLoading] = useState(true);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);
   const [error, setError] = useState<string | null>(null);
@@ -88,8 +90,8 @@ export function RecentCallsTable() {
       <Card className="bg-slate-800 border-slate-700">
       <Card className="bg-slate-800 border-slate-700">
         <div className="p-6">
         <div className="p-6">
           <div className="flex items-center justify-between mb-6">
           <div className="flex items-center justify-between mb-6">
-            <h3 className="text-lg font-semibold text-white">Recent Calls</h3>
-            <span className="text-sm text-slate-400">Last 24 hours</span>
+            <h3 className="text-lg font-semibold text-white">{t('dashboard.recentCalls.title')}</h3>
+            <span className="text-sm text-slate-400">{t('dashboard.recentCalls.timeframe')}</span>
           </div>
           </div>
 
 
           {loading ? (
           {loading ? (
@@ -102,21 +104,21 @@ export function RecentCallsTable() {
             </div>
             </div>
           ) : recentCalls.length === 0 ? (
           ) : recentCalls.length === 0 ? (
             <div className="text-center text-slate-400 py-12">
             <div className="text-center text-slate-400 py-12">
-              No recent calls to display
+              {t('dashboard.recentCalls.noCalls')}
             </div>
             </div>
           ) : (
           ) : (
             <div className="overflow-x-auto">
             <div className="overflow-x-auto">
               <table className="w-full">
               <table className="w-full">
                 <thead>
                 <thead>
                   <tr className="border-b border-slate-700">
                   <tr className="border-b border-slate-700">
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Time</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Customer</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Intent</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Outcome</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Duration</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Sentiment</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Cost</th>
-                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">Actions</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.time')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.customer')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.intent')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.outcome')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.duration')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.sentiment')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.cost')}</th>
+                    <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('dashboard.recentCalls.headers.actions')}</th>
                   </tr>
                   </tr>
                 </thead>
                 </thead>
                 <tbody>
                 <tbody>

+ 35 - 16
shopcall.ai-main/src/i18n/locales/de.json

@@ -132,31 +132,50 @@
     }
     }
   },
   },
   "dashboard": {
   "dashboard": {
-    "title": "Dashboard",
+    "title": "KI-Anruf-Dashboard",
+    "subtitle": "Ihre KI-Anrufstatistiken",
     "welcome": "Willkommen zurück",
     "welcome": "Willkommen zurück",
     "overview": "Übersicht",
     "overview": "Übersicht",
+    "quickFilters": {
+      "24h": "24h",
+      "7d": "7T",
+      "14d": "14T",
+      "30d": "30T",
+      "customRange": "Benutzerdefinierter Zeitraum"
+    },
+    "search": "Anrufe suchen...",
     "kpi": {
     "kpi": {
       "totalCalls": "Gesamtanrufe",
       "totalCalls": "Gesamtanrufe",
-      "activeCalls": "Aktive Anrufe",
-      "avgDuration": "Durchschn. Anrufdauer",
-      "successRate": "Erfolgsquote",
-      "totalRevenue": "Gesamtumsatz",
-      "costSavings": "Kosteneinsparungen"
+      "resolvedCalls": "Gelöste Anrufe",
+      "avgCallDuration": "Durchschn. Anrufdauer",
+      "totalCost": "Gesamtkosten",
+      "timeSaved": "Eingesparte Zeit",
+      "savedOnHumanCosts": "Einsparung bei Personalkosten",
+      "vsYesterday": "im Vergleich zu gestern",
+      "loading": "Metriken werden geladen...",
+      "noData": "Keine Daten verfügbar"
+    },
+    "recentCalls": {
+      "title": "Neueste Anrufe",
+      "timeframe": "Letzte 24 Stunden",
+      "loading": "Anrufe werden geladen...",
+      "error": "Fehler beim Laden der Anrufe",
+      "noCalls": "Keine aktuellen Anrufe anzuzeigen",
+      "headers": {
+        "time": "Zeit",
+        "customer": "Kunde",
+        "intent": "Absicht",
+        "outcome": "Ergebnis",
+        "duration": "Dauer",
+        "sentiment": "Stimmung",
+        "cost": "Kosten",
+        "actions": "Aktionen"
+      }
     },
     },
     "charts": {
     "charts": {
       "callVolume": "Anrufvolumen",
       "callVolume": "Anrufvolumen",
       "resolutionRate": "Lösungsrate",
       "resolutionRate": "Lösungsrate",
       "topIntents": "Häufigste Anrufabsichten"
       "topIntents": "Häufigste Anrufabsichten"
-    },
-    "recentCalls": {
-      "title": "Neueste Anrufe",
-      "viewAll": "Alle Anzeigen",
-      "customer": "Kunde",
-      "duration": "Dauer",
-      "status": "Status",
-      "intent": "Absicht",
-      "outcome": "Ergebnis",
-      "actions": "Aktionen"
     }
     }
   },
   },
   "sidebar": {
   "sidebar": {

+ 35 - 6
shopcall.ai-main/src/i18n/locales/en.json

@@ -132,16 +132,45 @@
     }
     }
   },
   },
   "dashboard": {
   "dashboard": {
-    "title": "Dashboard",
+    "title": "AI Call Dashboard",
+    "subtitle": "Your AI calling insights",
     "welcome": "Welcome back",
     "welcome": "Welcome back",
     "overview": "Overview",
     "overview": "Overview",
+    "quickFilters": {
+      "24h": "24h",
+      "7d": "7d",
+      "14d": "14d",
+      "30d": "30d",
+      "customRange": "Custom range"
+    },
+    "search": "Search calls...",
     "kpi": {
     "kpi": {
       "totalCalls": "Total Calls",
       "totalCalls": "Total Calls",
-      "activeCalls": "Active Calls",
-      "avgDuration": "Avg Call Duration",
-      "successRate": "Success Rate",
-      "totalRevenue": "Total Revenue",
-      "costSavings": "Cost Savings"
+      "resolvedCalls": "Resolved Calls",
+      "avgCallDuration": "Avg Call Duration",
+      "totalCost": "Total Cost",
+      "timeSaved": "Time Saved",
+      "savedOnHumanCosts": "Saved on Human Costs",
+      "vsYesterday": "vs yesterday",
+      "loading": "Loading metrics...",
+      "noData": "No data available"
+    },
+    "recentCalls": {
+      "title": "Recent Calls",
+      "timeframe": "Last 24 hours",
+      "loading": "Loading calls...",
+      "error": "Failed to load calls",
+      "noCalls": "No recent calls to display",
+      "headers": {
+        "time": "Time",
+        "customer": "Customer",
+        "intent": "Intent",
+        "outcome": "Outcome",
+        "duration": "Duration",
+        "sentiment": "Sentiment",
+        "cost": "Cost",
+        "actions": "Actions"
+      }
     },
     },
     "charts": {
     "charts": {
       "callVolume": "Call Volume",
       "callVolume": "Call Volume",

+ 38 - 19
shopcall.ai-main/src/i18n/locales/hu.json

@@ -132,31 +132,50 @@
     }
     }
   },
   },
   "dashboard": {
   "dashboard": {
-    "title": "Irányítópult",
+    "title": "AI Hívás Irányítópult",
+    "subtitle": "Az Ön AI hívási statisztikái",
     "welcome": "Üdvözöljük újra",
     "welcome": "Üdvözöljük újra",
     "overview": "Áttekintés",
     "overview": "Áttekintés",
+    "quickFilters": {
+      "24h": "24ó",
+      "7d": "7n",
+      "14d": "14n",
+      "30d": "30n",
+      "customRange": "Egyéni időszak"
+    },
+    "search": "Hívások keresése...",
     "kpi": {
     "kpi": {
-      "totalCalls": "Összes Hívás",
-      "activeCalls": "Aktív Hívások",
-      "avgDuration": "Átl. Híváshossz",
-      "successRate": "Sikerességi Arány",
-      "totalRevenue": "Teljes Bevétel",
-      "costSavings": "Költségmegtakarítás"
+      "totalCalls": "Összes hívás",
+      "resolvedCalls": "Megoldott hívások",
+      "avgCallDuration": "Átlagos hívási idő",
+      "totalCost": "Összes költség",
+      "timeSaved": "Megtakarított idő",
+      "savedOnHumanCosts": "Megtakarítás emberi költségeken",
+      "vsYesterday": "tegnapi naphoz képest",
+      "loading": "Metrikák betöltése...",
+      "noData": "Nincs elérhető adat"
+    },
+    "recentCalls": {
+      "title": "Legutóbbi hívások",
+      "timeframe": "Utolsó 24 óra",
+      "loading": "Hívások betöltése...",
+      "error": "Hívások betöltése sikertelen",
+      "noCalls": "Nincs megjeleníthető legutóbbi hívás",
+      "headers": {
+        "time": "Idő",
+        "customer": "Ügyfél",
+        "intent": "Cél",
+        "outcome": "Eredmény",
+        "duration": "Időtartam",
+        "sentiment": "Hangulat",
+        "cost": "Költség",
+        "actions": "Műveletek"
+      }
     },
     },
     "charts": {
     "charts": {
       "callVolume": "Hívásmennyiség",
       "callVolume": "Hívásmennyiség",
-      "resolutionRate": "Megoldási Arány",
-      "topIntents": "Leggyakoribb Hívási Szándékok"
-    },
-    "recentCalls": {
-      "title": "Legutóbbi Hívások",
-      "viewAll": "Összes Megtekintése",
-      "customer": "Ügyfél",
-      "duration": "Időtartam",
-      "status": "Állapot",
-      "intent": "Szándék",
-      "outcome": "Eredmény",
-      "actions": "Műveletek"
+      "resolutionRate": "Megoldási arány",
+      "topIntents": "Leggyakoribb hívási célok"
     }
     }
   },
   },
   "sidebar": {
   "sidebar": {