149 lines
6.6 KiB
TypeScript
149 lines
6.6 KiB
TypeScript
"use client";
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useAuth } from "../../hooks/useAuth";
|
|
import { useClients } from "../../hooks/useClients";
|
|
import { useCountries } from "../../hooks/useCountries";
|
|
import Sidebar from "../../components/Sidebar";
|
|
import Header from "../../components/Header";
|
|
import { checkupAPI, clientAPI } from "../../lib/api";
|
|
import { toast } from "react-toastify";
|
|
import { Activity, Play, Loader2, Monitor, AlertCircle } from "lucide-react";
|
|
|
|
export default function CheckupPage() {
|
|
const router = useRouter();
|
|
const { user, loading } = useAuth();
|
|
const { clients } = useClients({ status: "active", limit: 100 });
|
|
const { countries } = useCountries(true);
|
|
const [running, setRunning] = useState(false);
|
|
const [selectedClient, setSelectedClient] = useState("");
|
|
const [selectedCountry, setSelectedCountry] = useState("");
|
|
const [logs, setLogs] = useState<string[]>([]);
|
|
|
|
useEffect(() => { if (!loading && !user) router.push("/login"); }, [user, loading, router]);
|
|
|
|
const handleRunCheckup = async () => {
|
|
if (!selectedClient || !selectedCountry) {
|
|
toast.error("Select a client and country");
|
|
return;
|
|
}
|
|
setRunning(true);
|
|
setLogs([]);
|
|
try {
|
|
setLogs((prev) => [...prev, "Starting checkup session..."]);
|
|
const { data } = await checkupAPI.run({ client_id: selectedClient, country_id: selectedCountry });
|
|
setLogs((prev) => [...prev, "Checkup completed!", `Available dates: ${data.data?.available_dates?.length || 0} found`]);
|
|
toast.success("Checkup completed successfully");
|
|
} catch (error: any) {
|
|
const msg = error.response?.data?.message || "Checkup failed";
|
|
setLogs((prev) => [...prev, `Error: ${msg}`]);
|
|
toast.error(msg);
|
|
} finally {
|
|
setRunning(false);
|
|
}
|
|
};
|
|
|
|
if (loading || !user) return null;
|
|
|
|
return (
|
|
<div className="flex min-h-screen bg-dark-900">
|
|
<Sidebar />
|
|
<div className="flex-1 flex flex-col">
|
|
<Header />
|
|
<main className="flex-1 p-6 overflow-y-auto">
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold text-white flex items-center gap-2">
|
|
<Activity className="w-6 h-6 text-accent-cyan" />
|
|
Checkup Mode
|
|
</h1>
|
|
<p className="text-gray-500">Check visa appointment availability</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
{/* Control Panel */}
|
|
<div className="card">
|
|
<h2 className="text-lg font-semibold text-white mb-4">Run Checkup</h2>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="label">Select Client</label>
|
|
<select value={selectedClient} onChange={(e) => setSelectedClient(e.target.value)} className="select w-full">
|
|
<option value="">Choose a client...</option>
|
|
{clients.map((c: any) => (
|
|
<option key={c.id} value={c.id}>{c.first_name} {c.last_name} ({c.nationality || "N/A"})</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="label">Select Country</label>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{countries.map((c: any) => (
|
|
<button
|
|
key={c.id}
|
|
onClick={() => setSelectedCountry(c.id)}
|
|
className={`flex items-center gap-2 p-3 rounded-lg border transition-all ${
|
|
selectedCountry === c.id
|
|
? "border-accent-blue bg-accent-blue/10 text-white"
|
|
: "border-dark-600 hover:border-dark-500 text-gray-400"
|
|
}`}
|
|
>
|
|
<span className="text-xl">{c.flag_emoji}</span>
|
|
<span className="text-sm font-medium">{c.name}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={handleRunCheckup}
|
|
disabled={running || !selectedClient || !selectedCountry}
|
|
className="btn-primary w-full flex items-center justify-center gap-2 disabled:opacity-50"
|
|
>
|
|
{running ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
|
|
{running ? "Running Checkup..." : "Start Checkup"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Logs Panel */}
|
|
<div className="card">
|
|
<h2 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
|
<Monitor className="w-5 h-5 text-accent-blue" />
|
|
Session Logs
|
|
</h2>
|
|
<div className="bg-dark-900 rounded-lg p-4 h-80 overflow-y-auto font-mono text-sm space-y-1">
|
|
{logs.length === 0 ? (
|
|
<p className="text-gray-600 italic">No logs yet. Start a checkup to see logs here.</p>
|
|
) : (
|
|
logs.map((log, i) => (
|
|
<div key={i} className={`${log.includes("Error") ? "text-red-400" : log.includes("completed") ? "text-green-400" : "text-gray-400"}`}>
|
|
<span className="text-gray-600">[{new Date().toLocaleTimeString()}]</span> {log}
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Info Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
|
|
<div className="card">
|
|
<AlertCircle className="w-8 h-8 text-accent-orange mb-2" />
|
|
<h3 className="font-medium text-white mb-1">Automated Checkup</h3>
|
|
<p className="text-sm text-gray-500">Uses Playwright to navigate VFS websites and check for available appointment slots.</p>
|
|
</div>
|
|
<div className="card">
|
|
<Activity className="w-8 h-8 text-accent-cyan mb-2" />
|
|
<h3 className="font-medium text-white mb-1">Real-time Monitoring</h3>
|
|
<p className="text-sm text-gray-500">Screenshots are captured during the checkup process for verification.</p>
|
|
</div>
|
|
<div className="card">
|
|
<Monitor className="w-8 h-8 text-accent-purple mb-2" />
|
|
<h3 className="font-medium text-white mb-1">Session Logging</h3>
|
|
<p className="text-sm text-gray-500">All actions are logged and stored for audit and debugging purposes.</p>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|