Browse Source

feat(dashboard): refactor to use call_logs for real-time stats

- Update dashboard stats endpoint to calculate from call_logs table
- Show 4 KPI cards: Total Calls, Today's Calls, Avg Duration, Total Duration
- Remove cost-related UI elements (costs not fully implemented)
- Simplify RecentCallsTable to show time, caller, duration
- Add "View All" button linking to /call-logs page
- Remove ChartsSection (no data available yet)
- Fix duplicate call-logs endpoint that caused user_id column error
- Add caller to call log detail page title
- Fix filter button background in call logs page
- Update translations for en, hu, de

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fszontagh 4 months ago
parent
commit
dbed963f1f

+ 8 - 3
shopcall.ai-main/src/components/CallLogDetailContent.tsx

@@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react";
 import { useParams, useNavigate } from "react-router-dom";
 import { Button } from "@/components/ui/button";
 import { Card } from "@/components/ui/card";
-import { ArrowLeft, Phone, Clock, DollarSign, Calendar, Bot, User, Play, Pause } from "lucide-react";
+import { ArrowLeft, Phone, Clock, Calendar, Bot, User, Play, Pause } from "lucide-react";
 import { useTranslation } from "react-i18next";
 import { supabase } from "@/lib/supabase";
 import { API_URL } from "@/lib/config";
@@ -192,7 +192,9 @@ export function CallLogDetailContent() {
             {t('common.back')}
           </Button>
           <div>
-            <h1 className="text-2xl font-semibold text-white">{t('callLogDetail.title')}</h1>
+            <h1 className="text-2xl font-semibold text-white">
+              {t('callLogDetail.title')}{callLog.caller ? ` - ${callLog.caller}` : ''}
+            </h1>
             <p className="text-slate-400">{formatDateTime(callLog.started_at)}</p>
           </div>
         </div>
@@ -248,6 +250,7 @@ export function CallLogDetailContent() {
                   </div>
                 </div>
 
+                {/* Cost item hidden - cost calculation not fully implemented
                 <div className="flex items-center gap-3">
                   <div className="w-10 h-10 bg-yellow-500/10 rounded-lg flex items-center justify-center">
                     <DollarSign className="w-5 h-5 text-yellow-400" />
@@ -259,6 +262,7 @@ export function CallLogDetailContent() {
                     </p>
                   </div>
                 </div>
+                */}
               </div>
             </Card>
 
@@ -302,7 +306,7 @@ export function CallLogDetailContent() {
               </Card>
             )}
 
-            {/* Costs Breakdown Card */}
+            {/* Costs Breakdown Card - hidden, cost calculation not fully implemented
             {callLog.costs && Array.isArray(callLog.costs) && callLog.costs.length > 0 && (
               <Card className="bg-slate-800 border-slate-700 p-6">
                 <h3 className="text-lg font-semibold text-white mb-4">{t('callLogDetail.costsBreakdown')}</h3>
@@ -323,6 +327,7 @@ export function CallLogDetailContent() {
                 </div>
               </Card>
             )}
+            */}
           </div>
 
           {/* Right Column - Transcript Chat */}

+ 7 - 2
shopcall.ai-main/src/components/CallLogsContent.tsx

@@ -107,7 +107,8 @@ export function CallLogsContent() {
       time: log.started_at || log.created_at,
       caller: log.caller || '',
       duration: formatDuration(log.duration),
-      cost: log.cost_total ? `$${log.cost_total.toFixed(4)}` : '',
+      // cost hidden - cost calculation not fully implemented
+      // cost: log.cost_total ? `$${log.cost_total.toFixed(4)}` : '',
     }));
     exportCallLogs(exportData, format, filename);
   };
@@ -163,7 +164,7 @@ export function CallLogsContent() {
               />
             </div>
 
-            <Button variant="outline" className="border-slate-700 text-slate-300 hover:text-white hover:bg-slate-700">
+            <Button variant="outline" className="bg-slate-800 border-slate-700 text-slate-300 hover:text-white hover:bg-slate-700">
               <Filter className="w-4 h-4 mr-2" />
               {t('callLogs.filters')}
             </Button>
@@ -235,7 +236,9 @@ export function CallLogsContent() {
                         <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.endedAt')}</th>
                         <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.duration')}</th>
                         <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.caller')}</th>
+                        {/* Cost column hidden - cost calculation not fully implemented
                         <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.cost')}</th>
+                        */}
                         <th className="text-right py-3 px-4 text-sm font-medium text-slate-400"></th>
                       </tr>
                     </thead>
@@ -250,9 +253,11 @@ export function CallLogsContent() {
                           <td className="py-3 px-4 text-sm text-slate-300">{formatDateTime(call.ended_at)}</td>
                           <td className="py-3 px-4 text-sm text-slate-300">{formatDuration(call.duration)}</td>
                           <td className="py-3 px-4 text-sm text-white">{maskPhoneNumber(call.caller)}</td>
+                          {/* Cost cell hidden - cost calculation not fully implemented
                           <td className="py-3 px-4 text-sm text-slate-300">
                             {call.cost_total ? `$${call.cost_total.toFixed(4)}` : '-'}
                           </td>
+                          */}
                           <td className="py-3 px-4 text-right">
                             <ChevronRight className="w-4 h-4 text-slate-400 inline-block" />
                           </td>

+ 0 - 2
shopcall.ai-main/src/components/DashboardContent.tsx

@@ -1,6 +1,5 @@
 import { DashboardHeader } from "./DashboardHeader";
 import { KPICards } from "./KPICards";
-import { ChartsSection } from "./ChartsSection";
 import { RecentCallsTable } from "./RecentCallsTable";
 import { DashboardProvider } from "./context/DashboardContext";
 
@@ -12,7 +11,6 @@ export function DashboardContent() {
 
         <div className="p-6 space-y-6">
           <KPICards />
-          <ChartsSection />
           <RecentCallsTable />
         </div>
       </div>

+ 19 - 36
shopcall.ai-main/src/components/KPICards.tsx

@@ -1,5 +1,5 @@
 import { Card } from "@/components/ui/card";
-import { Phone, CheckCircle, Clock, TrendingUp, DollarSign, UserX, Loader2 } from "lucide-react";
+import { Phone, Calendar, Clock, Timer, Loader2 } from "lucide-react";
 import { useDashboard } from "./context/DashboardContext";
 import { useTranslation } from "react-i18next";
 
@@ -9,8 +9,8 @@ export function KPICards() {
 
   if (loading) {
     return (
-      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
-        {[...Array(6)].map((_, i) => (
+      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
+        {[...Array(4)].map((_, i) => (
           <Card key={i} className="bg-slate-800 border-slate-700 p-6">
             <div className="flex items-center justify-center h-32">
               <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
@@ -23,7 +23,7 @@ export function KPICards() {
 
   if (error || !stats) {
     return (
-      <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-4 gap-6">
         <Card className="bg-slate-800 border-slate-700 p-6 col-span-full">
           <div className="text-center text-slate-400">
             {error || t('dashboard.kpi.noData')}
@@ -39,17 +39,17 @@ export function KPICards() {
       value: stats.totalCalls.value.toString(),
       change: stats.totalCalls.change,
       changeType: stats.totalCalls.changeType,
-      subtitle: t('dashboard.kpi.vsYesterday'),
+      subtitle: t('dashboard.kpi.allTime'),
       icon: Phone,
       iconColor: "text-blue-500",
     },
     {
-      title: t('dashboard.kpi.resolvedCalls'),
-      value: stats.resolvedCalls.value.toString(),
-      change: stats.resolvedCalls.change,
-      changeType: stats.resolvedCalls.changeType,
+      title: t('dashboard.kpi.todayCalls'),
+      value: stats.todayCalls.value.toString(),
+      change: stats.todayCalls.change,
+      changeType: stats.todayCalls.changeType,
       subtitle: t('dashboard.kpi.vsYesterday'),
-      icon: CheckCircle,
+      icon: Calendar,
       iconColor: "text-green-500",
     },
     {
@@ -62,36 +62,18 @@ export function KPICards() {
       iconColor: "text-orange-500",
     },
     {
-      title: t('dashboard.kpi.totalCost'),
-      value: stats.totalCost.formatted,
-      change: stats.totalCost.change,
-      changeType: stats.totalCost.changeType,
-      subtitle: t('dashboard.kpi.vsYesterday'),
-      icon: DollarSign,
-      iconColor: "text-green-500",
-    },
-    {
-      title: t('dashboard.kpi.timeSaved'),
-      value: stats.timeSaved.formatted,
-      change: stats.timeSaved.change,
-      changeType: stats.timeSaved.changeType,
-      subtitle: t('dashboard.kpi.vsYesterday'),
-      icon: TrendingUp,
+      title: t('dashboard.kpi.totalDuration'),
+      value: stats.totalDuration.formatted,
+      change: stats.totalDuration.change,
+      changeType: stats.totalDuration.changeType,
+      subtitle: t('dashboard.kpi.allTime'),
+      icon: Timer,
       iconColor: "text-purple-500",
     },
-    {
-      title: t('dashboard.kpi.savedOnHumanCosts'),
-      value: stats.humanCostSaved.formatted,
-      change: stats.humanCostSaved.change,
-      changeType: stats.humanCostSaved.changeType,
-      subtitle: t('dashboard.kpi.vsYesterday'),
-      icon: UserX,
-      iconColor: "text-green-500",
-    },
   ];
 
   return (
-    <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-4 gap-6">
       {kpiData.map((kpi) => (
         <Card key={kpi.title} className="bg-slate-800 border-slate-700 p-6">
           <div className="flex items-center justify-between mb-4">
@@ -104,7 +86,8 @@ export function KPICards() {
 
             <div className="flex items-center gap-2 text-sm">
               <span className={`font-medium ${
-                kpi.changeType === 'positive' ? 'text-green-500' : 'text-red-500'
+                kpi.changeType === 'positive' ? 'text-green-500' :
+                kpi.changeType === 'negative' ? 'text-red-500' : 'text-slate-400'
               }`}>
                 {kpi.change}
               </span>

+ 92 - 123
shopcall.ai-main/src/components/RecentCallsTable.tsx

@@ -1,56 +1,60 @@
 import { Card } from "@/components/ui/card";
 import { Button } from "@/components/ui/button";
-import { Play, MoreHorizontal, Loader2 } from "lucide-react";
+import { ChevronRight, Loader2 } from "lucide-react";
 import { useState, useEffect } from "react";
-import { CallDetailsModal } from "./CallDetailsModal";
+import { useNavigate } from "react-router-dom";
 import { API_URL } from "@/lib/config";
+import { supabase } from "@/lib/supabase";
 import { useTranslation } from "react-i18next";
 
 interface CallLog {
-  time: string;
-  customer: string;
-  intent: string;
-  outcome: string;
-  duration: string;
-  sentiment: string;
-  cost: string;
-  outcomeColor: string;
-  sentimentColor: string;
+  id: string;
+  store_id: string;
+  created_at: string;
+  started_at: string | null;
+  ended_at: string | null;
+  duration: number | null;
+  caller: string | null;
 }
 
-const getSentimentEmoji = (sentiment: string) => {
-  switch (sentiment) {
-    case "Positive":
-      return "😊";
-    case "Negative":
-      return "😠";
-    case "Neutral":
-      return "😐";
-    default:
-      return "😐";
-  }
+const formatDuration = (seconds: number | null): string => {
+  if (!seconds) return "-";
+  const mins = Math.floor(seconds / 60);
+  const secs = Math.round(seconds % 60);
+  return `${mins}:${secs.toString().padStart(2, '0')}`;
+};
+
+const formatDateTime = (dateStr: string | null): string => {
+  if (!dateStr) return "-";
+  const date = new Date(dateStr);
+  return date.toLocaleString();
+};
+
+const maskPhoneNumber = (phone: string | null): string => {
+  if (!phone) return "-";
+  if (phone.length <= 4) return phone;
+  const last4 = phone.slice(-4);
+  return `***${last4}`;
 };
 
 export function RecentCallsTable() {
   const { t } = useTranslation();
+  const navigate = useNavigate();
   const [recentCalls, setRecentCalls] = useState<CallLog[]>([]);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);
-  const [selectedCall, setSelectedCall] = useState(null);
-  const [isModalOpen, setIsModalOpen] = useState(false);
 
   useEffect(() => {
     const fetchRecentCalls = async () => {
       try {
-        const sessionData = localStorage.getItem('session_data');
-        if (!sessionData) {
-          throw new Error('No session data found');
+        const { data: { session } } = await supabase.auth.getSession();
+        if (!session) {
+          throw new Error('Not authenticated');
         }
 
-        const session = JSON.parse(sessionData);
         const response = await fetch(`${API_URL}/api/call-logs`, {
           headers: {
-            'Authorization': `Bearer ${session.session.access_token}`,
+            'Authorization': `Bearer ${session.access_token}`,
             'Content-Type': 'application/json'
           }
         });
@@ -75,104 +79,69 @@ export function RecentCallsTable() {
     fetchRecentCalls();
   }, []);
 
-  const handleDetailsClick = (call) => {
-    setSelectedCall(call);
-    setIsModalOpen(true);
-  };
-
-  const handleCloseModal = () => {
-    setIsModalOpen(false);
-    setSelectedCall(null);
+  const handleRowClick = (callId: string) => {
+    navigate(`/call-logs/${callId}`);
   };
 
   return (
-    <>
-      <Card className="bg-slate-800 border-slate-700">
-        <div className="p-6">
-          <div className="flex items-center justify-between mb-6">
-            <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>
-
-          {loading ? (
-            <div className="flex items-center justify-center py-12">
-              <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
-            </div>
-          ) : error ? (
-            <div className="text-center text-slate-400 py-12">
-              {error}
-            </div>
-          ) : recentCalls.length === 0 ? (
-            <div className="text-center text-slate-400 py-12">
-              {t('dashboard.recentCalls.noCalls')}
-            </div>
-          ) : (
-            <div className="overflow-x-auto">
-              <table className="w-full">
-                <thead>
-                  <tr className="border-b border-slate-700">
-                    <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>
-                </thead>
-                <tbody>
-                  {recentCalls.map((call, index) => (
-                    <tr key={index} className="border-b border-slate-700/50 hover:bg-slate-700/30">
-                      <td className="py-3 px-4 text-sm text-slate-300">{call.time}</td>
-                      <td className="py-3 px-4 text-sm text-white">{call.customer}</td>
-                      <td className="py-3 px-4 text-sm text-slate-300">{call.intent}</td>
-                      <td className="py-3 px-4">
-                        <span className={`text-sm font-medium ${call.outcomeColor}`}>
-                          {call.outcome}
-                        </span>
-                      </td>
-                      <td className="py-3 px-4 text-sm text-slate-300">{call.duration}</td>
-                      <td className="py-3 px-4">
-                        <div className="flex items-center gap-2">
-                          <span className="text-sm">{getSentimentEmoji(call.sentiment)}</span>
-                          <span className={`text-sm ${call.sentimentColor}`}>
-                            {call.sentiment}
-                          </span>
-                        </div>
-                      </td>
-                      <td className="py-3 px-4 text-sm text-slate-300">{call.cost}</td>
-                      <td className="py-3 px-4">
-                        <div className="flex items-center gap-2">
-                          <Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
-                            <Play className="w-4 h-4" />
-                          </Button>
-                          <Button
-                            variant="ghost"
-                            size="sm"
-                            className="text-slate-400 hover:text-white"
-                            onClick={() => handleDetailsClick(call)}
-                          >
-                            <MoreHorizontal className="w-4 h-4" />
-                          </Button>
-                        </div>
-                      </td>
-                    </tr>
-                  ))}
-                </tbody>
-              </table>
-            </div>
-          )}
+    <Card className="bg-slate-800 border-slate-700">
+      <div className="p-6">
+        <div className="flex items-center justify-between mb-6">
+          <h3 className="text-lg font-semibold text-white">{t('dashboard.recentCalls.title')}</h3>
+          <Button
+            variant="ghost"
+            size="sm"
+            className="text-cyan-400 hover:text-cyan-300"
+            onClick={() => navigate('/call-logs')}
+          >
+            {t('dashboard.recentCalls.viewAll')}
+            <ChevronRight className="w-4 h-4 ml-1" />
+          </Button>
         </div>
-      </Card>
 
-      {selectedCall && (
-        <CallDetailsModal
-          isOpen={isModalOpen}
-          onClose={handleCloseModal}
-          call={selectedCall}
-        />
-      )}
-    </>
+        {loading ? (
+          <div className="flex items-center justify-center py-12">
+            <Loader2 className="w-8 h-8 text-slate-400 animate-spin" />
+          </div>
+        ) : error ? (
+          <div className="text-center text-slate-400 py-12">
+            {error}
+          </div>
+        ) : recentCalls.length === 0 ? (
+          <div className="text-center text-slate-400 py-12">
+            {t('dashboard.recentCalls.noCalls')}
+          </div>
+        ) : (
+          <div className="overflow-x-auto">
+            <table className="w-full">
+              <thead>
+                <tr className="border-b border-slate-700">
+                  <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.caller')}</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-right py-3 px-4 text-sm font-medium text-slate-400"></th>
+                </tr>
+              </thead>
+              <tbody>
+                {recentCalls.map((call) => (
+                  <tr
+                    key={call.id}
+                    className="border-b border-slate-700/50 hover:bg-slate-700/30 cursor-pointer transition-colors"
+                    onClick={() => handleRowClick(call.id)}
+                  >
+                    <td className="py-3 px-4 text-sm text-slate-300">{formatDateTime(call.started_at)}</td>
+                    <td className="py-3 px-4 text-sm text-white">{maskPhoneNumber(call.caller)}</td>
+                    <td className="py-3 px-4 text-sm text-slate-300">{formatDuration(call.duration)}</td>
+                    <td className="py-3 px-4 text-right">
+                      <ChevronRight className="w-4 h-4 text-slate-400 inline-block" />
+                    </td>
+                  </tr>
+                ))}
+              </tbody>
+            </table>
+          </div>
+        )}
+      </div>
+    </Card>
   );
 }

+ 17 - 16
shopcall.ai-main/src/components/context/DashboardContext.tsx

@@ -1,20 +1,22 @@
 import { createContext, useContext, useState, useEffect, ReactNode } from "react";
 import { API_URL } from "@/lib/config";
+import { supabase } from "@/lib/supabase";
+
+interface RecentCall {
+  id: string;
+  time: string;
+  caller: string | null;
+  duration: string;
+  store_id: string;
+}
 
 interface DashboardStats {
   totalCalls: { value: number; change: string; changeType: string };
-  resolvedCalls: { value: number; change: string; changeType: string };
+  todayCalls: { value: number; change: string; changeType: string };
+  weekCalls: { value: number; change: string; changeType: string };
   avgDuration: { value: number; formatted: string; change: string; changeType: string };
-  totalCost: { value: number; formatted: string; change: string; changeType: string };
-  timeSaved: { value: string; formatted: string; change: string; changeType: string };
-  humanCostSaved: { value: number; formatted: string; change: string; changeType: string };
-  resolutionRate: { value: number; dailyChange: string; weeklyChange: string };
-  topIntents: Array<{
-    name: string;
-    count: number;
-    percentage: number;
-    change: string;
-  }>;
+  totalDuration: { value: number; formatted: string; change: string; changeType: string };
+  recentCalls: RecentCall[];
 }
 
 interface DashboardContextType {
@@ -36,15 +38,14 @@ export function DashboardProvider({ children }: { children: ReactNode }) {
       setLoading(true);
       setError(null);
 
-      const sessionData = localStorage.getItem('session_data');
-      if (!sessionData) {
-        throw new Error('No session data found');
+      const { data: { session } } = await supabase.auth.getSession();
+      if (!session) {
+        throw new Error('Not authenticated');
       }
 
-      const session = JSON.parse(sessionData);
       const response = await fetch(`${API_URL}/api/dashboard/stats`, {
         headers: {
-          'Authorization': `Bearer ${session.session.access_token}`,
+          'Authorization': `Bearer ${session.access_token}`,
           'Content-Type': 'application/json'
         }
       });

+ 7 - 12
shopcall.ai-main/src/i18n/locales/de.json

@@ -147,30 +147,25 @@
     "noCallData": "Noch keine Anrufdaten verfügbar",
     "kpi": {
       "totalCalls": "Gesamtanrufe",
-      "resolvedCalls": "Gelöste Anrufe",
-      "avgCallDuration": "Durchschn. Anrufdauer",
-      "totalCost": "Gesamtkosten",
-      "timeSaved": "Eingesparte Zeit",
-      "savedOnHumanCosts": "Einsparung bei Personalkosten",
+      "todayCalls": "Heutige Anrufe",
+      "avgCallDuration": "Durchschn. Dauer",
+      "totalDuration": "Gesamtdauer",
       "vsYesterday": "im Vergleich zu gestern",
+      "allTime": "insgesamt",
       "loading": "Metriken werden geladen...",
       "noData": "Keine Daten verfügbar"
     },
     "recentCalls": {
       "title": "Neueste Anrufe",
+      "viewAll": "Alle anzeigen",
       "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"
+        "caller": "Anrufer",
+        "duration": "Dauer"
       }
     },
     "charts": {

+ 7 - 22
shopcall.ai-main/src/i18n/locales/en.json

@@ -147,46 +147,31 @@
     "noCallData": "No call data available yet",
     "kpi": {
       "totalCalls": "Total Calls",
-      "resolvedCalls": "Resolved Calls",
-      "avgCallDuration": "Avg Call Duration",
-      "totalCost": "Total Cost",
-      "timeSaved": "Time Saved",
-      "savedOnHumanCosts": "Saved on Human Costs",
+      "todayCalls": "Today's Calls",
+      "avgCallDuration": "Avg Duration",
+      "totalDuration": "Total Duration",
       "vsYesterday": "vs yesterday",
+      "allTime": "all time",
       "loading": "Loading metrics...",
       "noData": "No data available"
     },
     "recentCalls": {
       "title": "Recent Calls",
+      "viewAll": "View All",
       "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"
+        "caller": "Caller",
+        "duration": "Duration"
       }
     },
     "charts": {
       "callVolume": "Call Volume",
       "resolutionRate": "Resolution Rate",
       "topIntents": "Top Call Intents"
-    },
-    "recentCalls": {
-      "title": "Recent Calls",
-      "viewAll": "View All",
-      "customer": "Customer",
-      "duration": "Duration",
-      "status": "Status",
-      "intent": "Intent",
-      "outcome": "Outcome",
-      "actions": "Actions"
     }
   },
   "sidebar": {

+ 7 - 12
shopcall.ai-main/src/i18n/locales/hu.json

@@ -147,30 +147,25 @@
     "noCallData": "Még nincs hívásadat elérhető",
     "kpi": {
       "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",
+      "todayCalls": "Mai hívások",
+      "avgCallDuration": "Átlagos időtartam",
+      "totalDuration": "Összes időtartam",
       "vsYesterday": "tegnapi naphoz képest",
+      "allTime": "összesen",
       "loading": "Metrikák betöltése...",
       "noData": "Nincs elérhető adat"
     },
     "recentCalls": {
       "title": "Legutóbbi hívások",
+      "viewAll": "Összes megtekintése",
       "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"
+        "caller": "Hívó",
+        "duration": "Időtartam"
       }
     },
     "charts": {

+ 133 - 80
supabase/functions/api/index.ts

@@ -1802,108 +1802,161 @@ serve(async (req) => {
         )
       }
     }
-    // GET /api/dashboard/stats - Get dashboard statistics
+    // GET /api/dashboard/stats - Get dashboard statistics from call_logs
     if (path === 'dashboard/stats' && req.method === 'GET') {
-      // TODO: Implement real dashboard stats calculation
-      // For now, returning mock/empty data to unblock the frontend
-      return new Response(
-        JSON.stringify({
-          success: true,
-          stats: {
-            totalCalls: { value: 0, change: '0%', changeType: 'neutral' },
-            resolvedCalls: { value: 0, change: '0%', changeType: 'neutral' },
-            avgDuration: { value: 0, formatted: '0:00', change: '0%', changeType: 'neutral' },
-            totalCost: { value: 0, formatted: '$0.00', change: '0%', changeType: 'neutral' },
-            timeSaved: { value: '0h', formatted: '0 hours', change: '0%', changeType: 'neutral' },
-            humanCostSaved: { value: 0, formatted: '$0.00', change: '0%', changeType: 'neutral' },
-            resolutionRate: { value: 0, dailyChange: '0%', weeklyChange: '0%' },
-            topIntents: []
-          }
-        }),
-        { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
-      )
-    }
-
-    // GET /api/call-logs - Get call logs
-    if (path === 'call-logs' && req.method === 'GET') {
       try {
-        // Fetch call logs from database
-        const { data: callLogs, error: callLogsError } = await supabase
-          .from('call_logs')
-          .select('*')
+        // Get date boundaries
+        const now = new Date()
+        const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate())
+        const yesterdayStart = new Date(todayStart.getTime() - 24 * 60 * 60 * 1000)
+        const weekStart = new Date(todayStart.getTime() - 7 * 24 * 60 * 60 * 1000)
+
+        // Get user's store IDs first
+        const { data: stores, error: storesError } = await supabase
+          .from('stores')
+          .select('id')
           .eq('user_id', user.id)
-          .order('created_at', { ascending: false })
 
-        if (callLogsError) {
-          console.error('Error fetching call logs:', callLogsError)
+        if (storesError) {
+          console.error('Error fetching stores:', storesError)
+          throw new Error('Failed to fetch stores')
+        }
+
+        const storeIds = (stores || []).map(s => s.id)
+
+        if (storeIds.length === 0) {
+          // No stores, return empty stats
           return new Response(
-            JSON.stringify({ error: 'Failed to fetch call logs' }),
-            { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
+            JSON.stringify({
+              success: true,
+              stats: {
+                totalCalls: { value: 0, change: '0%', changeType: 'neutral' },
+                todayCalls: { value: 0, change: '0%', changeType: 'neutral' },
+                avgDuration: { value: 0, formatted: '0:00', change: '0%', changeType: 'neutral' },
+                totalDuration: { value: 0, formatted: '0h 0m', change: '0%', changeType: 'neutral' },
+                recentCalls: []
+              }
+            }),
+            { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
           )
         }
 
-        // Transform call logs to match frontend format
-        const transformedCallLogs = (callLogs || []).map(log => {
-          // Format duration
-          const duration = log.duration_seconds
-            ? `${Math.floor(log.duration_seconds / 60)}:${String(log.duration_seconds % 60).padStart(2, '0')}`
-            : '0:00'
-
-          // Determine outcome color
-          let outcomeColor = 'text-slate-300'
-          if (log.call_outcome === 'resolved' || log.call_outcome === 'interested') {
-            outcomeColor = 'text-green-500'
-          } else if (log.call_outcome === 'not_interested' || log.call_outcome === 'failed') {
-            outcomeColor = 'text-red-500'
-          } else if (log.call_outcome === 'pending') {
-            outcomeColor = 'text-yellow-500'
-          }
+        // Get all call logs for user's stores
+        const { data: allCalls, error: callsError } = await supabase
+          .from('call_logs')
+          .select('id, created_at, started_at, ended_at, duration, caller, store_id')
+          .in('store_id', storeIds)
+          .order('created_at', { ascending: false })
 
-          // Determine sentiment from analysis or summary
-          let sentiment = 'Neutral'
-          let sentimentColor = 'text-slate-300'
+        if (callsError) {
+          console.error('Error fetching call logs:', callsError)
+          throw new Error('Failed to fetch call logs')
+        }
 
-          if (log.analysis?.successEvaluation === 'true' || log.analysis?.successEvaluation === true) {
-            sentiment = 'Positive'
-            sentimentColor = 'text-green-500'
-          } else if (log.analysis?.successEvaluation === 'false' || log.analysis?.successEvaluation === false) {
-            sentiment = 'Negative'
-            sentimentColor = 'text-red-500'
-          }
+        const calls = allCalls || []
+
+        // Calculate stats
+        const totalCalls = calls.length
+        const todayCalls = calls.filter(c => new Date(c.created_at) >= todayStart).length
+        const yesterdayCalls = calls.filter(c => {
+          const date = new Date(c.created_at)
+          return date >= yesterdayStart && date < todayStart
+        }).length
+        const weekCalls = calls.filter(c => new Date(c.created_at) >= weekStart).length
+
+        // Calculate average duration (in seconds)
+        const callsWithDuration = calls.filter(c => c.duration && parseFloat(c.duration) > 0)
+        const totalDurationSeconds = callsWithDuration.reduce((sum, c) => sum + parseFloat(c.duration || '0'), 0)
+        const avgDurationSeconds = callsWithDuration.length > 0 ? totalDurationSeconds / callsWithDuration.length : 0
+
+        // Calculate today's avg duration vs yesterday
+        const todayCallsWithDuration = calls.filter(c =>
+          new Date(c.created_at) >= todayStart && c.duration && parseFloat(c.duration) > 0
+        )
+        const yesterdayCallsWithDuration = calls.filter(c => {
+          const date = new Date(c.created_at)
+          return date >= yesterdayStart && date < todayStart && c.duration && parseFloat(c.duration) > 0
+        })
 
-          // Format cost
-          const cost = log.cost_total ? `$${Number(log.cost_total).toFixed(4)}` : '$0.00'
+        const todayAvgDuration = todayCallsWithDuration.length > 0
+          ? todayCallsWithDuration.reduce((sum, c) => sum + parseFloat(c.duration || '0'), 0) / todayCallsWithDuration.length
+          : 0
+        const yesterdayAvgDuration = yesterdayCallsWithDuration.length > 0
+          ? yesterdayCallsWithDuration.reduce((sum, c) => sum + parseFloat(c.duration || '0'), 0) / yesterdayCallsWithDuration.length
+          : 0
+
+        // Calculate changes
+        const callsChange = yesterdayCalls > 0
+          ? Math.round(((todayCalls - yesterdayCalls) / yesterdayCalls) * 100)
+          : (todayCalls > 0 ? 100 : 0)
+        const durationChange = yesterdayAvgDuration > 0
+          ? Math.round(((todayAvgDuration - yesterdayAvgDuration) / yesterdayAvgDuration) * 100)
+          : 0
+
+        // Format duration helper
+        const formatDuration = (seconds: number): string => {
+          if (seconds < 60) return `${Math.round(seconds)}s`
+          const mins = Math.floor(seconds / 60)
+          const secs = Math.round(seconds % 60)
+          return `${mins}:${secs.toString().padStart(2, '0')}`
+        }
 
-          // Format time
-          const time = new Date(log.created_at).toLocaleString()
+        const formatTotalDuration = (seconds: number): string => {
+          const hours = Math.floor(seconds / 3600)
+          const mins = Math.floor((seconds % 3600) / 60)
+          if (hours > 0) return `${hours}h ${mins}m`
+          return `${mins}m`
+        }
 
-          return {
-            id: log.id,
-            time,
-            customer: log.customer_number || 'Unknown',
-            intent: log.summary || 'N/A',
-            outcome: log.call_outcome || 'pending',
-            duration,
-            sentiment,
-            cost,
-            outcomeColor,
-            sentimentColor,
-            // Include full log data for details modal
-            fullData: log
-          }
-        })
+        // Get recent calls (last 5)
+        const recentCalls = calls.slice(0, 5).map(call => ({
+          id: call.id,
+          time: call.started_at || call.created_at,
+          caller: call.caller || null,
+          duration: call.duration ? formatDuration(parseFloat(call.duration)) : '-',
+          store_id: call.store_id
+        }))
 
         return new Response(
           JSON.stringify({
             success: true,
-            call_logs: transformedCallLogs
+            stats: {
+              totalCalls: {
+                value: totalCalls,
+                change: `${callsChange >= 0 ? '+' : ''}${callsChange}%`,
+                changeType: callsChange >= 0 ? 'positive' : 'negative'
+              },
+              todayCalls: {
+                value: todayCalls,
+                change: `${callsChange >= 0 ? '+' : ''}${callsChange}%`,
+                changeType: callsChange >= 0 ? 'positive' : 'negative'
+              },
+              weekCalls: {
+                value: weekCalls,
+                change: '0%',
+                changeType: 'neutral'
+              },
+              avgDuration: {
+                value: Math.round(avgDurationSeconds),
+                formatted: formatDuration(avgDurationSeconds),
+                change: `${durationChange >= 0 ? '+' : ''}${durationChange}%`,
+                changeType: durationChange >= 0 ? 'positive' : 'negative'
+              },
+              totalDuration: {
+                value: Math.round(totalDurationSeconds),
+                formatted: formatTotalDuration(totalDurationSeconds),
+                change: '0%',
+                changeType: 'neutral'
+              },
+              recentCalls
+            }
           }),
           { status: 200, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       } catch (error) {
-        console.error('Error in call-logs endpoint:', error)
+        console.error('Error in dashboard/stats endpoint:', error)
         return new Response(
-          JSON.stringify({ error: 'Internal server error' }),
+          JSON.stringify({ error: 'Failed to fetch dashboard stats' }),
           { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
         )
       }