import React, { useEffect, useMemo, useState } from “react”; import { Users, Bell, Upload, CheckCircle, AlertTriangle, Calendar, Search, Clock, Download, Plus, X, Trash2, PhoneCall, Sparkles, Loader2, Send, LogIn, Shield, Settings, Home, FileText, LogOut, } from “lucide-react”; /** * VAT Control Panel — Website (single-file React) * ————————————————- * What this gives you: * – Landing page (public) * – Login page (simple demo auth) * – Dashboard app (your panel) with improved logic: * ✅ clock updates (deadline banner stays correct) * ✅ local-date safe storage (no timezone drift) * ✅ AI retry spinner fixed (no flicker) * ✅ call note prefilled per client * ✅ export JSON backup * * Production notes: * – Replace demo auth with real auth (Supabase / NextAuth) * – Replace /api/generate-call-script with a real server route */ // ———- Small helpers ———- const cn = (…classes) => classes.filter(Boolean).join(” “); function todayLocalYYYYMMDD(date = new Date()) { const yyyy = date.getFullYear(); const mm = String(date.getMonth() + 1).padStart(2, “0”); const dd = String(date.getDate()).padStart(2, “0”); return `${yyyy}-${mm}-${dd}`; } function monthKeyFrom(year, monthIndex) { const mm = String(monthIndex + 1).padStart(2, “0”); return `${year}-${mm}`; } function safeParseJSON(str, fallback) { try { return JSON.parse(str); } catch { return fallback; } } // ———- UI primitives ———- const Card = ({ className, children }) => (
{children}
); const Pill = ({ className, children }) => ( {children} ); const IconButton = ({ title, onClick, className, children, disabled }) => ( ); // ———- App ———- export default function VATControlPanelWebsite() { // — “Routing” (single-file demo) — const [route, setRoute] = useState(() => { const saved = localStorage.getItem(“vat_route_v1”); return saved || “landing”; // landing | login | app }); useEffect(() => { localStorage.setItem(“vat_route_v1”, route); }, [route]); // — Demo Auth — const [session, setSession] = useState(() => { const saved = localStorage.getItem(“vat_session_v1”); return saved ? safeParseJSON(saved, null) : null; }); useEffect(() => { localStorage.setItem(“vat_session_v1”, JSON.stringify(session)); }, [session]); const logout = () => { setSession(null); setRoute(“landing”); }; // Guard useEffect(() => { if (route === “app” && !session) setRoute(“login”); }, [route, session]); return (
{route === “landing” && ( setRoute(“login”)} onOpenApp={() => setRoute(session ? “app” : “login”)} /> )} {route === “login” && ( setRoute(“landing”)} onSuccess={(user) => { setSession(user); setRoute(“app”); }} /> )} {route === “app” && session && ( setRoute(“landing”)} onLogout={logout} /> )}
); } // ———- Landing ———- function Landing({ onLogin, onOpenApp }) { return (
VAT
VAT Control Panel
Client tracking • deadline control • call logs

Track VAT files. Hit every deadline.

A clean dashboard to track client submissions by month, flag overdue accounts, log calls, and export backups. Simple, fast, and built for real consultancy workflow.

} title=”Deadline Alerts” desc=”Countdown + late period flags.” /> } title=”Call Logs” desc=”Notes + follow-up tracking.” /> } title=”Submission Mark” desc=”One click received status.” /> } title=”Export Backup” desc=”Download JSON anytime.” />
Preview
Monthly VAT Cycle
Made for Bangladesh workflow
Track files before the 10th, chase overdue clients from 11th–20th, and start next month cycle after 20th.
© {new Date().getFullYear()} VAT Control Panel • Built for consultancy ops
); } function Feature({ icon, title, desc }) { return (
{icon} {title}
{desc}
); } function PreviewRow({ label, status }) { const styles = status === “UP TO DATE” ? “bg-green-100 text-green-700 border-green-200” : status === “OVERDUE” ? “bg-red-100 text-red-700 border-red-200” : “bg-amber-100 text-amber-700 border-amber-200”; return (
{label}
{status === “UP TO DATE” ? ( ) : ( )} {status}
); } // ———- Login (demo) ———- function Login({ onBack, onSuccess }) { const [email, setEmail] = useState(() => localStorage.getItem(“vat_admin_email”) || “”); const [password, setPassword] = useState(“”); const [error, setError] = useState(“”); const submit = (e) => { e.preventDefault(); setError(“”); // Demo auth rule: // – first time: set password in localStorage // – later: must match const existing = localStorage.getItem(“vat_admin_pass”); if (!email.trim()) return setError(“Enter your email.”); if (password.length < 4) return setError("Password must be at least 4 characters."); localStorage.setItem("vat_admin_email", email.trim()); if (!existing) { localStorage.setItem("vat_admin_pass", password); onSuccess({ email: email.trim(), role: "Admin" }); return; } if (password !== existing) { return setError("Wrong password."); } onSuccess({ email: email.trim(), role: "Admin" }); }; return (
Admin Login
Demo login (replace with real auth in production)
setEmail(e.target.value)} className=”mt-1 w-full px-4 py-3 rounded-2xl bg-slate-50 border border-slate-200 focus:ring-2 focus:ring-indigo-500 outline-none” placeholder=”admin@yourfirm.com” />
setPassword(e.target.value)} className=”mt-1 w-full px-4 py-3 rounded-2xl bg-slate-50 border border-slate-200 focus:ring-2 focus:ring-indigo-500 outline-none” placeholder=”••••” />
{error && (
{error}
)}
First time? This will set your password in this browser (demo).
Production Upgrade
Make it real login
  • Supabase Auth (email/password) or NextAuth
  • Database sync so all devices see the same clients
  • Role access: Admin / Staff / Viewer
  • Server API for AI so your key stays secure
); } // ———- Dashboard App ———- function DashboardApp({ user, onExit, onLogout }) { // Settings (persisted) const [deadlineDay, setDeadlineDay] = useState(() => Number(localStorage.getItem(“vat_deadline_day”) || 10)); const [resetDay, setResetDay] = useState(() => Number(localStorage.getItem(“vat_reset_day”) || 20)); const [firmName, setFirmName] = useState(() => localStorage.getItem(“vat_firm_name”) || “Parves & Associates”); useEffect(() => { localStorage.setItem(“vat_deadline_day”, String(deadlineDay)); localStorage.setItem(“vat_reset_day”, String(resetDay)); localStorage.setItem(“vat_firm_name”, firmName); }, [deadlineDay, resetDay, firmName]); // Live time const [currentTime, setCurrentTime] = useState(() => new Date()); useEffect(() => { const id = setInterval(() => setCurrentTime(new Date()), 60_000); return () => clearInterval(id); }, []); const currentDay = currentTime.getDate(); const currentMonth = currentTime.getMonth(); const currentYear = currentTime.getFullYear(); // Cycle logic: after resetDay -> next month cycle const targetMonthIndex = currentDay > resetDay ? (currentMonth + 1) % 12 : currentMonth; const targetYear = currentDay > resetDay && currentMonth === 11 ? currentYear + 1 : currentYear; const targetMonthName = new Date(targetYear, targetMonthIndex).toLocaleString(“default”, { month: “long” }); const targetPeriodKey = monthKeyFrom(targetYear, targetMonthIndex); // YYYY-MM // Data persistence const [clients, setClients] = useState(() => { const saved = localStorage.getItem(“vat_clients_v5”); if (saved) return safeParseJSON(saved, []); return [ { id: 1, name: “Acme Corp”, // store lastUpload as YYYY-MM-DD local lastUpload: “2026-02-05”, documents: 12, lastCall: “2026-02-01”, callNotes: “Requested missing fuel receipts”, }, { id: 2, name: “Global Logistics Ltd”, lastUpload: “N/A”, documents: 0, lastCall: “2026-02-08”, callNotes: “Left voicemail regarding Q1 filing”, }, ]; }); useEffect(() => { localStorage.setItem(“vat_clients_v5”, JSON.stringify(clients)); }, [clients]); // UI State const [searchTerm, setSearchTerm] = useState(“”); const [showAddModal, setShowAddModal] = useState(false); const [showUploadModal, setShowUploadModal] = useState(null); const [showCallModal, setShowCallModal] = useState(null); const [showSettings, setShowSettings] = useState(false); const [newClientName, setNewClientName] = useState(“”); const [callNote, setCallNote] = useState(“”); // AI const [isAiLoading, setIsAiLoading] = useState(false); const [aiSuggestion, setAiSuggestion] = useState(“”); const exportBackup = () => { const blob = new Blob([JSON.stringify({ firmName, deadlineDay, resetDay, clients }, null, 2)], { type: “application/json”, }); const url = URL.createObjectURL(blob); const a = document.createElement(“a”); a.href = url; a.download = `vat_backup_${todayLocalYYYYMMDD()}.json`; a.click(); }; // — Server-side AI call (recommended) — async function callAI(prompt, systemInstruction) { setIsAiLoading(true); setAiSuggestion(“”); const delays = [1000, 2000, 4000, 8000, 16000]; try { let lastErr = null; for (let i = 0; i < delays.length; i++) { try { const response = await fetch("/api/generate-call-script", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt, systemInstruction }), }); if (!response.ok) throw new Error(`API failed: ${response.status}`); const result = await response.json(); // Flexible parsing (works with multiple providers) const text = result?.text || result?.candidates?.[0]?.content?.parts?.[0]?.text || result?.output || ""; setAiSuggestion(String(text).trim() || "✨ No suggestion returned."); return; } catch (e) { lastErr = e; await new Promise((res) => setTimeout(res, delays[i])); } } throw lastErr; } catch { // Local fallback (so UI still works in demo) setAiSuggestion( “✨ AI Strategy (Offline Mode)\n\nHi, this is a quick VAT follow-up call.\n1) Confirm this month’s document set for filing.\n2) Ask for missing invoices/receipts (fuel, utilities, bank).\n3) Set a delivery date (today/tomorrow).\n4) Confirm any changes in sales/purchase volume.\n\nClose: ‘Please send the files today so we can submit before the deadline.’” ); } finally { setIsAiLoading(false); } } const generateCallStrategy = (client) => { const status = getStatus(client.lastUpload); const systemPrompt = “You are an expert VAT consultant assistant. Keep output short, clear, and actionable.”; const userPrompt = `Generate a short call script for client “${client.name}”.\nStatus: ${status}.\nPeriod: ${targetMonthName} ${targetYear} (${targetPeriodKey}).\nLast call notes: ${client.callNotes || “None”}.\nInclude: greeting, what to request, deadline reminder, and a firm closing line.`; callAI(userPrompt, systemPrompt); }; const addClient = (e) => { e.preventDefault(); if (!newClientName.trim()) return; const newClient = { id: Date.now(), name: newClientName.trim(), lastUpload: “N/A”, documents: 0, lastCall: “N/A”, callNotes: “”, }; setClients([newClient, …clients]); setNewClientName(“”); setShowAddModal(false); }; const handleUpload = (clientId) => { const today = todayLocalYYYYMMDD(); setClients((prev) => prev.map((client) => { if (client.id === clientId) { return { …client, documents: (client.documents || 0) + 1, lastUpload: today, }; } return client; }) ); setShowUploadModal(null); }; const handleCallLog = (clientId) => { const today = todayLocalYYYYMMDD(); setClients((prev) => prev.map((client) => { if (client.id === clientId) { return { …client, lastCall: today, callNotes: callNote, }; } return client; }) ); setCallNote(“”); setShowCallModal(null); }; const deleteClient = (id) => { if (window.confirm(“Are you sure you want to remove this client?”)) { setClients(clients.filter((c) => c.id !== id)); } }; // Status logic based on target period month const getStatus = (lastUpload) => { // never uploaded if (lastUpload === “N/A”) { return currentDay > deadlineDay && currentDay <= resetDay ? "OVERDUE" : "PENDING"; } // If stored as YYYY-MM-DD const upload = new Date(`${lastUpload}T00:00:00`); const uploadMonth = upload.getMonth(); const uploadYear = upload.getFullYear(); const isUpToDate = uploadMonth === targetMonthIndex && uploadYear === targetYear; if (isUpToDate) return "UP TO DATE"; if (currentDay > deadlineDay && currentDay <= resetDay) return "OVERDUE"; return "PENDING"; }; const getStatusStyles = (status) => { switch (status) { case “UP TO DATE”: return “bg-green-100 text-green-700 border-green-200”; case “OVERDUE”: return “bg-red-100 text-red-700 border-red-200”; default: return “bg-amber-100 text-amber-700 border-amber-200”; } }; const filteredClients = useMemo(() => { return clients.filter((client) => client.name.toLowerCase().includes(searchTerm.toLowerCase()) ); }, [clients, searchTerm]); // UI return (
{/* Sidebar */}
VAT
{/* Top bar */}

VAT Control Panel

Tracking Cycle: {targetMonthName} {targetYear}

{firmName} • {user.email}

{currentDay <= deadlineDay ? (

Filing Deadline

Collect files before the {deadlineDay}th.

{deadlineDay – currentDay}{” “} Days
) : currentDay > resetDay ? (

Pre-Month Cycle

Collecting early for {targetMonthName}.

) : (
Late Period

Chase overdue clients immediately.

)}
{/* Action Bar */}
setSearchTerm(e.target.value)} className=”pl-10 pr-4 py-2.5 bg-white border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 w-full shadow-sm” />
{/* Main Table */}
{filteredClients.map((client) => { const status = getStatus(client.lastUpload); const statusStyle = getStatusStyles(status); const lastCallDate = client.lastCall !== “N/A” ? new Date(`${client.lastCall}T00:00:00`) : null; const callOverdue = !lastCallDate || new Date() – lastCallDate > 1000 * 60 * 60 * 24 * 7; return ( ); })}
Client Status ({targetMonthName}) History Last Lead Call Actions
{client.name.charAt(0)}

{client.name}

ID: {String(client.id).slice(-5)}

{status === “UP TO DATE” ? ( ) : ( )} {status}
{client.documents} Submissions Last: {client.lastUpload}
{client.lastCall === “N/A” ? “No log” : client.lastCall}
{client.callNotes && ( “{client.callNotes}” )}
{ setShowCallModal(client); setAiSuggestion(“”); setCallNote(client.callNotes || “”); }} className=”bg-indigo-50 text-indigo-600 hover:bg-indigo-600 hover:text-white” > setShowUploadModal(client)} className=”bg-slate-100 text-slate-600 hover:bg-slate-800 hover:text-white” > deleteClient(client.id)} className=”text-slate-300 hover:text-red-500 shadow-none” >
{/* Call Log Modal */} {showCallModal && (

Call Log

Period: {targetMonthName} {targetYear}

{showCallModal.name}