Browse Source

feat(call-logs): add call log detail page with chat transcript view

- Update CallLogsContent to show started_at, ended_at, duration, caller, cost
- Add clickable rows that navigate to detail page
- Create CallLogDetailContent with two-column layout:
  - Left: call stats, audio player, costs breakdown
  - Right: chat-style transcript with AI/User icons
- Add CallLogDetail page wrapper
- Add route for /call-logs/:id
- Add 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
6561cb4801

+ 2 - 0
shopcall.ai-main/src/App.tsx

@@ -21,6 +21,7 @@ import Signup from "./pages/Signup";
 const Dashboard = lazy(() => import("./pages/Dashboard"));
 const OTP = lazy(() => import("./pages/OTP"));
 const CallLogs = lazy(() => import("./pages/CallLogs"));
+const CallLogDetail = lazy(() => import("./pages/CallLogDetail"));
 const Analytics = lazy(() => import("./pages/Analytics"));
 const Webshops = lazy(() => import("./pages/Webshops"));
 const PhoneNumbers = lazy(() => import("./pages/PhoneNumbers"));
@@ -74,6 +75,7 @@ const App = () => (
                 {/* Pages that don't require an active store */}
                 <Route path="/dashboard" element={<Dashboard />} />
                 <Route path="/call-logs" element={<CallLogs />} />
+                <Route path="/call-logs/:id" element={<CallLogDetail />} />
                 <Route path="/analytics" element={<Analytics />} />
                 <Route path="/webshops" element={<Webshops />} />
                 <Route path="/phone-numbers" element={<PhoneNumbers />} />

+ 368 - 0
shopcall.ai-main/src/components/CallLogDetailContent.tsx

@@ -0,0 +1,368 @@
+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 { useTranslation } from "react-i18next";
+import { supabase } from "@/integrations/supabase/client";
+
+interface CallLog {
+  id: string;
+  store_id: string;
+  created_at: string;
+  started_at: string | null;
+  ended_at: string | null;
+  duration: number | null;
+  caller: string | null;
+  transcript: string | null;
+  recording_url: string | null;
+  costs: any | null;
+  cost_total: number | null;
+  payload: any;
+}
+
+interface TranscriptMessage {
+  role: 'ai' | 'user';
+  content: string;
+}
+
+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 parseTranscript = (transcript: string | null): TranscriptMessage[] => {
+  if (!transcript) return [];
+
+  const messages: TranscriptMessage[] = [];
+  const lines = transcript.split('\n');
+
+  for (const line of lines) {
+    const trimmedLine = line.trim();
+    if (!trimmedLine) continue;
+
+    if (trimmedLine.startsWith('AI:')) {
+      messages.push({
+        role: 'ai',
+        content: trimmedLine.substring(3).trim()
+      });
+    } else if (trimmedLine.startsWith('User:')) {
+      messages.push({
+        role: 'user',
+        content: trimmedLine.substring(5).trim()
+      });
+    } else {
+      // If line doesn't have a prefix, append to last message or create new one
+      if (messages.length > 0) {
+        messages[messages.length - 1].content += ' ' + trimmedLine;
+      }
+    }
+  }
+
+  return messages;
+};
+
+export function CallLogDetailContent() {
+  const { t } = useTranslation();
+  const { id } = useParams<{ id: string }>();
+  const navigate = useNavigate();
+  const [callLog, setCallLog] = useState<CallLog | null>(null);
+  const [isLoading, setIsLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);
+  const [isPlaying, setIsPlaying] = useState(false);
+  const audioRef = useRef<HTMLAudioElement>(null);
+
+  useEffect(() => {
+    const fetchCallLog = async () => {
+      if (!id) return;
+
+      setIsLoading(true);
+      setError(null);
+
+      try {
+        const { data, error: fetchError } = await supabase
+          .from('call_logs')
+          .select('*')
+          .eq('id', id)
+          .single();
+
+        if (fetchError) {
+          throw fetchError;
+        }
+
+        setCallLog(data);
+      } catch (err) {
+        setError(err instanceof Error ? err.message : 'An error occurred');
+      } finally {
+        setIsLoading(false);
+      }
+    };
+
+    fetchCallLog();
+  }, [id]);
+
+  const handleBack = () => {
+    navigate('/call-logs');
+  };
+
+  const togglePlayPause = () => {
+    if (audioRef.current) {
+      if (isPlaying) {
+        audioRef.current.pause();
+      } else {
+        audioRef.current.play();
+      }
+      setIsPlaying(!isPlaying);
+    }
+  };
+
+  const handleAudioEnded = () => {
+    setIsPlaying(false);
+  };
+
+  const messages = parseTranscript(callLog?.transcript || null);
+
+  if (isLoading) {
+    return (
+      <div className="flex-1 bg-slate-900 text-white flex items-center justify-center">
+        <div className="flex flex-col items-center">
+          <div className="relative">
+            <div className="w-12 h-12 border-4 border-slate-700 rounded-full"></div>
+            <div className="w-12 h-12 border-4 border-white border-t-transparent rounded-full animate-spin absolute top-0"></div>
+          </div>
+          <p className="text-slate-400 mt-4 font-medium">{t('common.loading')}</p>
+        </div>
+      </div>
+    );
+  }
+
+  if (error || !callLog) {
+    return (
+      <div className="flex-1 bg-slate-900 text-white flex items-center justify-center">
+        <div className="flex flex-col items-center">
+          <div className="w-16 h-16 bg-red-500/10 rounded-full flex items-center justify-center mb-4">
+            <svg className="w-8 h-8 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
+              <circle cx="12" cy="12" r="10"/>
+              <line x1="12" y1="8" x2="12" y2="12"/>
+              <line x1="12" y1="16" x2="12.01" y2="16"/>
+            </svg>
+          </div>
+          <p className="text-red-500 font-medium mb-2">{t('callLogDetail.notFound')}</p>
+          <Button
+            className="bg-slate-700 hover:bg-slate-600 text-white mt-4"
+            onClick={handleBack}
+          >
+            <ArrowLeft className="w-4 h-4 mr-2" />
+            {t('callLogDetail.backToList')}
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="flex-1 bg-slate-900 text-white overflow-hidden flex flex-col">
+      {/* Header */}
+      <div className="border-b border-slate-800 bg-slate-900">
+        <div className="flex items-center gap-4 p-6">
+          <Button
+            variant="ghost"
+            size="sm"
+            className="text-slate-400 hover:text-white"
+            onClick={handleBack}
+          >
+            <ArrowLeft className="w-4 h-4 mr-2" />
+            {t('common.back')}
+          </Button>
+          <div>
+            <h1 className="text-2xl font-semibold text-white">{t('callLogDetail.title')}</h1>
+            <p className="text-slate-400">{formatDateTime(callLog.started_at)}</p>
+          </div>
+        </div>
+      </div>
+
+      {/* Content - Two Column Layout */}
+      <div className="flex-1 overflow-hidden p-6">
+        <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 h-full">
+          {/* Left Column - Call Statistics */}
+          <div className="lg:col-span-1 space-y-6">
+            {/* Call Info Card */}
+            <Card className="bg-slate-800 border-slate-700 p-6">
+              <h3 className="text-lg font-semibold text-white mb-4">{t('callLogDetail.callInfo')}</h3>
+
+              <div className="space-y-4">
+                <div className="flex items-center gap-3">
+                  <div className="w-10 h-10 bg-cyan-500/10 rounded-lg flex items-center justify-center">
+                    <Calendar className="w-5 h-5 text-cyan-400" />
+                  </div>
+                  <div>
+                    <p className="text-sm text-slate-400">{t('callLogDetail.startedAt')}</p>
+                    <p className="text-white">{formatDateTime(callLog.started_at)}</p>
+                  </div>
+                </div>
+
+                <div className="flex items-center gap-3">
+                  <div className="w-10 h-10 bg-cyan-500/10 rounded-lg flex items-center justify-center">
+                    <Calendar className="w-5 h-5 text-cyan-400" />
+                  </div>
+                  <div>
+                    <p className="text-sm text-slate-400">{t('callLogDetail.endedAt')}</p>
+                    <p className="text-white">{formatDateTime(callLog.ended_at)}</p>
+                  </div>
+                </div>
+
+                <div className="flex items-center gap-3">
+                  <div className="w-10 h-10 bg-green-500/10 rounded-lg flex items-center justify-center">
+                    <Phone className="w-5 h-5 text-green-400" />
+                  </div>
+                  <div>
+                    <p className="text-sm text-slate-400">{t('callLogDetail.caller')}</p>
+                    <p className="text-white">{callLog.caller || '-'}</p>
+                  </div>
+                </div>
+
+                <div className="flex items-center gap-3">
+                  <div className="w-10 h-10 bg-purple-500/10 rounded-lg flex items-center justify-center">
+                    <Clock className="w-5 h-5 text-purple-400" />
+                  </div>
+                  <div>
+                    <p className="text-sm text-slate-400">{t('callLogDetail.duration')}</p>
+                    <p className="text-white">{formatDuration(callLog.duration)}</p>
+                  </div>
+                </div>
+
+                <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" />
+                  </div>
+                  <div>
+                    <p className="text-sm text-slate-400">{t('callLogDetail.cost')}</p>
+                    <p className="text-white">
+                      {callLog.cost_total ? `$${callLog.cost_total.toFixed(4)}` : '-'}
+                    </p>
+                  </div>
+                </div>
+              </div>
+            </Card>
+
+            {/* Audio Player Card */}
+            {callLog.recording_url && (
+              <Card className="bg-slate-800 border-slate-700 p-6">
+                <h3 className="text-lg font-semibold text-white mb-4">{t('callLogDetail.recording')}</h3>
+
+                <div className="space-y-4">
+                  <audio
+                    ref={audioRef}
+                    src={callLog.recording_url}
+                    onEnded={handleAudioEnded}
+                    className="hidden"
+                  />
+
+                  <div className="flex items-center gap-4">
+                    <Button
+                      onClick={togglePlayPause}
+                      className="w-12 h-12 rounded-full bg-cyan-600 hover:bg-cyan-500 flex items-center justify-center"
+                    >
+                      {isPlaying ? (
+                        <Pause className="w-5 h-5" />
+                      ) : (
+                        <Play className="w-5 h-5 ml-0.5" />
+                      )}
+                    </Button>
+                    <div className="flex-1">
+                      <p className="text-sm text-slate-400">{t('callLogDetail.clickToPlay')}</p>
+                      <p className="text-white text-sm">{formatDuration(callLog.duration)}</p>
+                    </div>
+                  </div>
+
+                  {/* Native audio controls as fallback */}
+                  <audio
+                    src={callLog.recording_url}
+                    controls
+                    className="w-full mt-2"
+                  />
+                </div>
+              </Card>
+            )}
+
+            {/* Costs Breakdown Card */}
+            {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>
+
+                <div className="space-y-2">
+                  {callLog.costs.map((cost: any, index: number) => (
+                    <div key={index} className="flex justify-between items-center py-2 border-b border-slate-700 last:border-0">
+                      <span className="text-slate-400 text-sm capitalize">{cost.type}</span>
+                      <span className="text-white text-sm">${cost.cost?.toFixed(4) || '0.0000'}</span>
+                    </div>
+                  ))}
+                  <div className="flex justify-between items-center pt-2 border-t border-slate-600">
+                    <span className="text-white font-medium">{t('callLogDetail.total')}</span>
+                    <span className="text-cyan-400 font-medium">
+                      ${callLog.cost_total?.toFixed(4) || '0.0000'}
+                    </span>
+                  </div>
+                </div>
+              </Card>
+            )}
+          </div>
+
+          {/* Right Column - Transcript Chat */}
+          <div className="lg:col-span-2 h-full">
+            <Card className="bg-slate-800 border-slate-700 h-full flex flex-col">
+              <div className="p-4 border-b border-slate-700">
+                <h3 className="text-lg font-semibold text-white">{t('callLogDetail.transcript')}</h3>
+              </div>
+
+              <div className="flex-1 overflow-y-auto p-4 space-y-4">
+                {messages.length === 0 ? (
+                  <div className="flex items-center justify-center h-full">
+                    <p className="text-slate-400">{t('callLogDetail.noTranscript')}</p>
+                  </div>
+                ) : (
+                  messages.map((message, index) => (
+                    <div
+                      key={index}
+                      className={`flex gap-3 ${message.role === 'ai' ? 'justify-start' : 'justify-end'}`}
+                    >
+                      {message.role === 'ai' && (
+                        <div className="w-8 h-8 bg-cyan-500/20 rounded-full flex items-center justify-center flex-shrink-0">
+                          <Bot className="w-4 h-4 text-cyan-400" />
+                        </div>
+                      )}
+
+                      <div
+                        className={`max-w-[80%] rounded-2xl px-4 py-3 ${
+                          message.role === 'ai'
+                            ? 'bg-slate-700 text-white rounded-tl-none'
+                            : 'bg-cyan-600 text-white rounded-tr-none'
+                        }`}
+                      >
+                        <p className="text-sm leading-relaxed">{message.content}</p>
+                      </div>
+
+                      {message.role === 'user' && (
+                        <div className="w-8 h-8 bg-green-500/20 rounded-full flex items-center justify-center flex-shrink-0">
+                          <User className="w-4 h-4 text-green-400" />
+                        </div>
+                      )}
+                    </div>
+                  ))
+                )}
+              </div>
+            </Card>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 82 - 101
shopcall.ai-main/src/components/CallLogsContent.tsx

@@ -1,77 +1,72 @@
 import { Button } from "@/components/ui/button";
 import { Input } from "@/components/ui/input";
 import { Card } from "@/components/ui/card";
-import { Search, Download, Calendar, Filter, Play, MoreHorizontal } from "lucide-react";
+import { Search, Download, Calendar, Filter, ChevronRight } from "lucide-react";
 import { useState, useEffect } from "react";
-import { CallDetailsModal } from "./CallDetailsModal";
+import { useNavigate } from "react-router-dom";
 import { ExportModal, ExportFormat } from "./ExportModal";
 import { exportCallLogs } from "@/lib/exportUtils";
-import { API_URL } from "@/lib/config";
 import { useTranslation } from "react-i18next";
+import { supabase } from "@/integrations/supabase/client";
 
 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;
+  transcript: string | null;
+  recording_url: string | null;
+  costs: any | null;
+  cost_total: number | null;
+  payload: any;
 }
 
-const maskPhoneNumber = (phone: string | undefined ) => {
-  if (!phone) return "";
-  const last4 = phone.slice(-4);
-  return `...xxx-${last4}`;
+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 getSentimentEmoji = (sentiment: string) => {
-  switch (sentiment) {
-    case "Positive":
-      return "😊";
-    case "Negative":
-      return "😠";
-    case "Neutral":
-      return "😐";
-    default:
-      return "😐";
-  }
+const maskPhoneNumber = (phone: string | null): string => {
+  if (!phone) return "-";
+  if (phone.length <= 4) return phone;
+  const last4 = phone.slice(-4);
+  return `***${last4}`;
 };
 
 export function CallLogsContent() {
   const { t } = useTranslation();
-  const [selectedCall, setSelectedCall] = useState<CallLog | null>(null);
-  const [isModalOpen, setIsModalOpen] = useState(false);
+  const navigate = useNavigate();
   const [isExportModalOpen, setIsExportModalOpen] = useState(false);
   const [callLogs, setCallLogs] = useState<CallLog[]>([]);
   const [isLoading, setIsLoading] = useState(true);
   const [error, setError] = useState<string | null>(null);
+  const [searchQuery, setSearchQuery] = useState("");
 
   const fetchCallLogs = async () => {
     setIsLoading(true);
     setError(null);
     try {
-      const sessionData = localStorage.getItem('session_data');
-      if (!sessionData) {
-        throw new Error('No session data found');
-      }
-
-      const { access_token } = JSON.parse(sessionData).session;
-      const response = await fetch(`${API_URL}/api/call-logs`, {
-        headers: {
-          'Authorization': `Bearer ${access_token}`,
-          'Content-Type': 'application/json',
-        },
-      });
+      const { data, error: fetchError } = await supabase
+        .from('call_logs')
+        .select('*')
+        .order('created_at', { ascending: false });
 
-      if (!response.ok) {
-        throw new Error('Failed to fetch call logs');
+      if (fetchError) {
+        throw fetchError;
       }
 
-      const data = await response.json();
-      setCallLogs(data.call_logs);
+      setCallLogs(data || []);
     } catch (err) {
       setError(err instanceof Error ? err.message : 'An error occurred');
     } finally {
@@ -83,14 +78,8 @@ export function CallLogsContent() {
     fetchCallLogs();
   }, []);
 
-  const handleDetailsClick = (call: CallLog) => {
-    setSelectedCall(call);
-    setIsModalOpen(true);
-  };
-
-  const handleCloseModal = () => {
-    setIsModalOpen(false);
-    setSelectedCall(null);
+  const handleRowClick = (callId: string) => {
+    navigate(`/call-logs/${callId}`);
   };
 
   const handleExportClick = () => {
@@ -104,9 +93,24 @@ export function CallLogsContent() {
   const handleExport = (format: ExportFormat) => {
     const timestamp = new Date().toISOString().split('T')[0];
     const filename = `call-logs-${timestamp}`;
-    exportCallLogs(callLogs, format, filename);
+    const exportData = callLogs.map(log => ({
+      time: log.started_at || log.created_at,
+      caller: log.caller || '',
+      duration: formatDuration(log.duration),
+      cost: log.cost_total ? `$${log.cost_total.toFixed(4)}` : '',
+    }));
+    exportCallLogs(exportData, format, filename);
   };
 
+  const filteredLogs = callLogs.filter(log => {
+    if (!searchQuery) return true;
+    const searchLower = searchQuery.toLowerCase();
+    return (
+      (log.caller && log.caller.toLowerCase().includes(searchLower)) ||
+      (log.transcript && log.transcript.toLowerCase().includes(searchLower))
+    );
+  });
+
   return (
     <>
       <div className="flex-1 bg-slate-900 text-white">
@@ -144,6 +148,8 @@ export function CallLogsContent() {
               <Input
                 placeholder={t('callLogs.searchPlaceholder')}
                 className="pl-10 bg-slate-800 border-slate-700 text-white placeholder-slate-400"
+                value={searchQuery}
+                onChange={(e) => setSearchQuery(e.target.value)}
               />
             </div>
 
@@ -206,56 +212,39 @@ export function CallLogsContent() {
                     {t('common.tryAgain')}
                   </Button>
                 </div>
+              ) : filteredLogs.length === 0 ? (
+                <div className="flex flex-col items-center justify-center py-12">
+                  <p className="text-slate-400">{t('callLogs.noCalls')}</p>
+                </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('callLogs.table.time')}</th>
-                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.customer')}</th>
-                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.intent')}</th>
-                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.outcome')}</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.startedAt')}</th>
+                        <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.sentiment')}</th>
+                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.caller')}</th>
                         <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.cost')}</th>
-                        <th className="text-left py-3 px-4 text-sm font-medium text-slate-400">{t('callLogs.table.actions')}</th>
+                        <th className="text-right py-3 px-4 text-sm font-medium text-slate-400"></th>
                       </tr>
                     </thead>
                     <tbody>
-                      {callLogs.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">{maskPhoneNumber(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>
+                      {filteredLogs.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-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>
+                          <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-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 className="py-3 px-4 text-right">
+                            <ChevronRight className="w-4 h-4 text-slate-400 inline-block" />
                           </td>
                         </tr>
                       ))}
@@ -268,14 +257,6 @@ export function CallLogsContent() {
         </div>
       </div>
 
-      {selectedCall && (
-        <CallDetailsModal
-          isOpen={isModalOpen}
-          onClose={handleCloseModal}
-          call={selectedCall}
-        />
-      )}
-
       <ExportModal
         isOpen={isExportModalOpen}
         onClose={handleCloseExportModal}

+ 22 - 1
shopcall.ai-main/src/i18n/locales/de.json

@@ -321,8 +321,12 @@
       "duration": "Dauer",
       "sentiment": "Stimmung",
       "cost": "Kosten",
-      "actions": "Aktionen"
+      "actions": "Aktionen",
+      "startedAt": "Begonnen",
+      "endedAt": "Beendet",
+      "caller": "Anrufer"
     },
+    "noCalls": "Keine Anrufe gefunden",
     "exportDialog": {
       "title": "Anrufprotokolle Exportieren",
       "description": "{{count}} Anrufprotokolle in Ihrem bevorzugten Format exportieren",
@@ -338,6 +342,23 @@
       }
     }
   },
+  "callLogDetail": {
+    "title": "Anrufdetails",
+    "backToList": "Zurück zur Anrufliste",
+    "notFound": "Anruf nicht gefunden",
+    "callInfo": "Anrufinformationen",
+    "startedAt": "Begonnen",
+    "endedAt": "Beendet",
+    "caller": "Anrufer",
+    "duration": "Dauer",
+    "cost": "Kosten",
+    "recording": "Aufnahme",
+    "clickToPlay": "Klicken zum Abspielen",
+    "costsBreakdown": "Kostenaufschlüsselung",
+    "total": "Gesamt",
+    "transcript": "Transkript",
+    "noTranscript": "Kein Transkript verfügbar"
+  },
   "analytics": {
     "title": "Analysen",
     "subtitle": "Tiefere Einblicke und Trends im Zeitverlauf",

+ 23 - 2
shopcall.ai-main/src/i18n/locales/en.json

@@ -321,11 +321,12 @@
     "subtitle": "Detailed history of all AI-handled calls",
     "export": "Export",
     "dateRange": "Date Range",
-    "searchPlaceholder": "Search by phone number or customer...",
+    "searchPlaceholder": "Search by phone number or transcript...",
     "filters": "Filters",
     "allCallLogs": "All Call Logs",
     "loadingCallLogs": "Loading call logs...",
     "failedToLoad": "Failed to load call logs",
+    "noCalls": "No calls found",
     "table": {
       "time": "Time",
       "customer": "Customer",
@@ -334,7 +335,10 @@
       "duration": "Duration",
       "sentiment": "Sentiment",
       "cost": "Cost",
-      "actions": "Actions"
+      "actions": "Actions",
+      "startedAt": "Started At",
+      "endedAt": "Ended At",
+      "caller": "Caller"
     },
     "exportDialog": {
       "title": "Export Call Logs",
@@ -351,6 +355,23 @@
       }
     }
   },
+  "callLogDetail": {
+    "title": "Call Details",
+    "backToList": "Back to Call Logs",
+    "notFound": "Call not found",
+    "callInfo": "Call Information",
+    "startedAt": "Started At",
+    "endedAt": "Ended At",
+    "caller": "Caller",
+    "duration": "Duration",
+    "cost": "Cost",
+    "recording": "Recording",
+    "clickToPlay": "Click to play recording",
+    "costsBreakdown": "Costs Breakdown",
+    "total": "Total",
+    "transcript": "Transcript",
+    "noTranscript": "No transcript available"
+  },
   "analytics": {
     "title": "Analytics",
     "subtitle": "Deeper insights and trends over time",

+ 22 - 1
shopcall.ai-main/src/i18n/locales/hu.json

@@ -324,8 +324,12 @@
       "duration": "Időtartam",
       "sentiment": "Hangulat",
       "cost": "Költség",
-      "actions": "Műveletek"
+      "actions": "Műveletek",
+      "startedAt": "Kezdés",
+      "endedAt": "Befejezés",
+      "caller": "Hívó"
     },
+    "noCalls": "Nem található hívás",
     "exportDialog": {
       "title": "Hívásnapló Exportálása",
       "description": "{{count}} hívásnapló exportálása a választott formátumban",
@@ -341,6 +345,23 @@
       }
     }
   },
+  "callLogDetail": {
+    "title": "Hívás Részletei",
+    "backToList": "Vissza a Hívásnaplóhoz",
+    "notFound": "Hívás nem található",
+    "callInfo": "Hívás Információk",
+    "startedAt": "Kezdés",
+    "endedAt": "Befejezés",
+    "caller": "Hívó",
+    "duration": "Időtartam",
+    "cost": "Költség",
+    "recording": "Felvétel",
+    "clickToPlay": "Kattintson a lejátszáshoz",
+    "costsBreakdown": "Költségek Bontása",
+    "total": "Összesen",
+    "transcript": "Átirat",
+    "noTranscript": "Nincs elérhető átirat"
+  },
   "analytics": {
     "title": "Elemzések",
     "subtitle": "Mélyebb betekintés és trendek időben",

+ 16 - 0
shopcall.ai-main/src/pages/CallLogDetail.tsx

@@ -0,0 +1,16 @@
+import { SidebarProvider } from "@/components/ui/sidebar";
+import { AppSidebar } from "@/components/AppSidebar";
+import { CallLogDetailContent } from "@/components/CallLogDetailContent";
+
+const CallLogDetail = () => {
+  return (
+    <SidebarProvider>
+      <div className="min-h-screen flex w-full bg-slate-900">
+        <AppSidebar />
+        <CallLogDetailContent />
+      </div>
+    </SidebarProvider>
+  );
+};
+
+export default CallLogDetail;