import { useState, useEffect } from 'react'; import { invoke } from '@tauri-apps/api/core'; import { Calendar, RefreshCw, LogIn, Video } from 'lucide-react'; import { openUrl } from '@tauri-apps/plugin-opener'; interface CalendarEvent { id: string; subject: string; start: { dateTime: string, timeZone: string }; end: { dateTime: string, timeZone: string }; onlineMeeting?: { joinUrl: string }; location?: { displayName: string }; bodyPreview?: string; // Text preview body?: { content: string, contentType: string }; // Full HTML/Text attendees?: { emailAddress: { name: string, address: string }, type: string, status: { response: string } }[]; } interface MeetingsViewProps { onStartRecording: (subject?: string) => void; azureClientId: string; apiKey: string; productId: string; selectedModel: string; onModelChange: (model: string) => void; } export default function MeetingsView({ onStartRecording, azureClientId, apiKey, productId, selectedModel, onModelChange }: MeetingsViewProps) { const [isAuthenticated, setIsAuthenticated] = useState(false); const [token, setToken] = useState(localStorage.getItem('m365_token') || ''); // const [clientId, setClientId] = useState(azureClientId); // Use prop directly // Sync prop to state if needed, or just use prop. // Let's us prop directly in startAuthFlow const [events, setEvents] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [expandedIds, setExpandedIds] = useState>(new Set()); const [availableModels, setAvailableModels] = useState>([]); const toggleExpand = (id: string) => { const newSet = new Set(expandedIds); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } setExpandedIds(newSet); }; useEffect(() => { if (apiKey && productId) { loadModels(); } }, [apiKey, productId]); const loadModels = async () => { try { const models = await invoke>('get_available_models', { apiKey, productId }); if (models && models.length > 0) { models.sort((a, b) => a.name.localeCompare(b.name)); setAvailableModels(models); } } catch (e) { console.error("Failed to load models:", e); } }; useEffect(() => { if (token) { setIsAuthenticated(true); fetchEvents(token); } }, [token]); const handleLogin = async () => { if (!azureClientId) { setError("Please configure Client ID in Settings"); return; } localStorage.setItem('m365_client_id', azureClientId); setLoading(true); setError(''); try { const accessToken = await invoke('start_auth_flow', { clientId: azureClientId }); setToken(accessToken); localStorage.setItem('m365_token', accessToken); setIsAuthenticated(true); fetchEvents(accessToken); } catch (err) { console.error("Auth failed", err); setError(String(err)); // Use String() to safely convert error object } finally { setLoading(false); } }; const fetchEvents = async (authToken: string) => { setLoading(true); setError(''); try { const data = await invoke('get_calendar_events', { token: authToken }); // Sort by start time const sorted = data.sort((a, b) => new Date(a.start.dateTime).getTime() - new Date(b.start.dateTime).getTime()); setEvents(sorted); } catch (err) { console.error("Fetch failed", err); setError(`Fetch failed: ${err}`); // If error is 401, logout if (String(err).includes('401')) { logout(); } } finally { setLoading(false); } }; const logout = () => { setToken(''); localStorage.removeItem('m365_token'); setIsAuthenticated(false); setEvents([]); }; const handleJoin = async (joinUrl?: string, subject?: string) => { if (!joinUrl) return; try { // 1. Open URL await openUrl(joinUrl); // 2. Start Recording (wait a sec for app focus switch?) // Actually user might want to confirm recording? Protocol says "one-click". onStartRecording(subject); } catch (e) { console.error("Failed to join", e); } }; const formatTime = (isoString: string) => { return new Date(isoString).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); }; const formatDate = (isoString: string) => { const date = new Date(isoString); const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); if (date.toDateString() === today.toDateString()) return "Today"; if (date.toDateString() === tomorrow.toDateString()) return "Tomorrow"; return date.toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric' }); }; // Group events by date const groupedEvents = events.reduce((groups, event) => { const dateKey = formatDate(event.start.dateTime); if (!groups[dateKey]) { groups[dateKey] = []; } groups[dateKey].push(event); return groups; }, {} as Record); return (

Upcoming Meetings

{/* Auth Section */} {!isAuthenticated ? (

Connect Microsoft 365

Connect your account to see upcoming Teams & Zoom meetings and join them with one click.

{azureClientId ? ( Client ID Configured ) : ( Client ID Missing in Settings )}
{error && (
Error: {error}
)}

Note: Requires an Azure App Registration (Multitenant) with redirect URI:
http://localhost:14200/auth/callback

) : (
Next 7 Days
{/* Model Selector */}
Using:
{events.length === 0 && !loading && (
{/* No meetings empty state (only if no error) */}

No upcoming meetings found for the next 7 days.

)} {error && (
{error}
)}
{Object.entries(groupedEvents).map(([dateLabel, dateEvents]) => (
{dateLabel}
{dateEvents.map(event => (
{formatTime(event.start.dateTime)} - {formatTime(event.end.dateTime)}

{event.subject}

{event.location?.displayName && (
📍 {event.location.displayName}
)}
{event.onlineMeeting?.joinUrl ? ( ) : (
No Link
)}
{/* Expand/Collapse Button */} {/* Expanded Content */} {expandedIds.has(event.id) && (
{event.body?.content ? (
) : (

{event.bodyPreview || "No details available."}

)} {event.attendees && event.attendees.length > 0 && (

👥 Attendees ({event.attendees.length})

{event.attendees.map((att, i) => (
{att.emailAddress.name || att.emailAddress.address}
))}
)}
)}
))}
))}
)}
); }