"use client"; import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import { Building2, CreditCard, Map, Wifi, Users, ChevronRight, } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/Card"; import { Button } from "@/components/ui/Button"; import { Input } from "@/components/ui/Input"; import { Table, TableHead, TableBody, TableRow, Th, Td, EmptyState } from "@/components/ui/Table"; import { Badge } from "@/components/ui/Badge"; import { Modal } from "@/components/ui/Modal"; import { formatCurrency } from "@/lib/utils"; import api from "@/lib/api"; // ─── Types ───────────────────────────────────────────────────────────────────── interface Tenant { id: string; name: string; slug: string; address?: string; phone?: string; email?: string; settings?: TenantBillingSettings | null; } interface TenantBillingSettings { billingDay?: number; lateFeeAmount?: number; lateFeeGraceDays?: number; } interface Plan { id: string; name: string; description?: string; type: string; speedDownMbps: number; speedUpMbps: number; monthlyPrice: number | string; isActive: boolean; } // ─── Sub-page: Tenant ───────────────────────────────────────────────────────── function TenantSettings() { const [name, setName] = useState(""); const [address, setAddress] = useState(""); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [loaded, setLoaded] = useState(false); const { isLoading } = useQuery({ queryKey: ["tenant-me"], queryFn: async () => { const res = await api.get("/api/v1/tenants/me"); return res.data; }, select: (data) => { if (!loaded) { setName(data.name ?? ""); setAddress(data.address ?? ""); setEmail(data.email ?? ""); setPhone(data.phone ?? ""); setLoaded(true); } return data; }, }); const saveMutation = useMutation({ mutationFn: async () => { await api.patch("/api/v1/tenants/me", { name, address, email, phone }); }, onSuccess: () => toast.success("Tenant settings saved"), onError: () => toast.error("Failed to save. Endpoint may not be available yet."), }); if (isLoading) { return
; } return ( Business Information
{ e.preventDefault(); saveMutation.mutate(); }} className="space-y-4 max-w-lg" > setName(e.target.value)} /> setAddress(e.target.value)} /> setEmail(e.target.value)} /> setPhone(e.target.value)} /> {saveMutation.isError && (

Save endpoint not available yet — changes not persisted.

)}
); } // ─── Sub-page: Billing Settings ─────────────────────────────────────────────── interface FullBillingSettings { billingDay?: number; gracePeriodDays?: number; lateFeeAmount?: string | number; lateFeePercent?: string | number; lateFeeGraceDays?: number; currency?: string; } function BillingSettings() { const [fields, setFields] = useState({ billingDay: "1", gracePeriodDays: "5", lateFeeAmount: "0", lateFeePercent: "0", lateFeeGraceDays: "0", currency: "PHP" }); const [loaded, setLoaded] = useState(false); const { isLoading } = useQuery({ queryKey: ["tenant-billing-settings"], queryFn: async () => { const res = await api.get("/api/v1/tenants/me/settings"); return res.data ?? {}; }, select: (data) => { if (!loaded && data) { setFields({ billingDay: String(data.billingDay ?? 1), gracePeriodDays: String(data.gracePeriodDays ?? 5), lateFeeAmount: String(data.lateFeeAmount ?? 0), lateFeePercent: String(data.lateFeePercent ?? 0), lateFeeGraceDays: String(data.lateFeeGraceDays ?? 0), currency: data.currency ?? "PHP", }); setLoaded(true); } return data; }, }); const saveMutation = useMutation({ mutationFn: async () => { await api.patch("/api/v1/tenants/me/settings", { billingDay: parseInt(fields.billingDay), gracePeriodDays: parseInt(fields.gracePeriodDays), lateFeeAmount: parseFloat(fields.lateFeeAmount), lateFeePercent: parseFloat(fields.lateFeePercent), lateFeeGraceDays: parseInt(fields.lateFeeGraceDays), currency: fields.currency, }); }, onSuccess: () => toast.success("Billing settings saved"), onError: () => toast.error("Failed to save billing settings"), }); const set = (key: keyof typeof fields) => (e: React.ChangeEvent) => setFields(f => ({ ...f, [key]: e.target.value })); if (isLoading) return
; return ( Billing Configuration
{ e.preventDefault(); saveMutation.mutate(); }} className="space-y-4 max-w-lg">
); } // ─── Sub-page: Areas & Zones ────────────────────────────────────────────────── interface Area { id: string; name: string; zones?: Zone[]; } interface Zone { id: string; name: string; areaId: string; } function AreasSettings() { const [showAddArea, setShowAddArea] = useState(false); const [showAddZone, setShowAddZone] = useState(false); const [areaName, setAreaName] = useState(""); const [zoneName, setZoneName] = useState(""); const [zoneAreaId, setZoneAreaId] = useState(""); const { data: areas, isLoading, refetch } = useQuery({ queryKey: ["areas"], queryFn: async () => { try { const res = await api.get("/api/v1/areas"); const d = res.data; return Array.isArray(d) ? d : (d as { data: Area[] }).data ?? []; } catch { return []; } }, }); const addAreaMutation = useMutation({ mutationFn: async () => { await api.post("/api/v1/areas", { name: areaName }); }, onSuccess: () => { toast.success("Area added"); setAreaName(""); setShowAddArea(false); refetch(); }, onError: () => toast.error("Failed to add area"), }); const addZoneMutation = useMutation({ mutationFn: async () => { await api.post("/api/v1/zones", { name: zoneName, areaId: zoneAreaId }); }, onSuccess: () => { toast.success("Zone added"); setZoneName(""); setZoneAreaId(""); setShowAddZone(false); refetch(); }, onError: () => toast.error("Failed to add zone"), }); const areaList = areas ?? []; return (
Areas & Zones
{isLoading ? ( Array.from({ length: 3 }).map((_, i) => ( )) ) : areaList.length === 0 ? ( ) : ( areaList.map((a) => ( )) )}
Area Name Zones
{a.name} {a.zones?.length ? a.zones.map((z) => z.name).join(", ") : No zones}
{/* Add Area Modal */} setShowAddArea(false)} title="Add Area">
{ e.preventDefault(); addAreaMutation.mutate(); }} className="space-y-4"> setAreaName(e.target.value)} placeholder="e.g. North Sector" />
{/* Add Zone Modal */} setShowAddZone(false)} title="Add Zone">
{ e.preventDefault(); addZoneMutation.mutate(); }} className="space-y-4">
setZoneName(e.target.value)} placeholder="e.g. Zone 1" />
); } // ─── Sub-page: Plans ────────────────────────────────────────────────────────── function PlansSettings() { const [showAdd, setShowAdd] = useState(false); const [planName, setPlanName] = useState(""); const [planType, setPlanType] = useState("POSTPAID"); const [speedDown, setSpeedDown] = useState(""); const [speedUp, setSpeedUp] = useState(""); const [price, setPrice] = useState(""); const [description, setDescription] = useState(""); const [editPlan, setEditPlan] = useState(null); const [editForm, setEditForm] = useState({ name: "", type: "POSTPAID", speedDown: "", speedUp: "", price: "", description: "" }); const { data, isLoading, refetch } = useQuery({ queryKey: ["plans"], queryFn: async () => { try { const res = await api.get("/api/v1/plans?includeInactive=true"); const d = res.data; return Array.isArray(d) ? d : (d as { data: Plan[] }).data ?? []; } catch { return []; } }, }); const addPlanMutation = useMutation({ mutationFn: async () => { await api.post("/api/v1/plans", { name: planName, type: planType, speedDownMbps: parseInt(speedDown), speedUpMbps: parseInt(speedUp), monthlyPrice: parseFloat(price), description: description || undefined, }); }, onSuccess: () => { toast.success("Plan created"); resetAdd(); setShowAdd(false); refetch(); }, onError: () => toast.error("Failed to create plan"), }); const toggleActiveMutation = useMutation({ mutationFn: async ({ id, isActive }: { id: string; isActive: boolean }) => { await api.patch(`/api/v1/plans/${id}`, { isActive: !isActive }); }, onSuccess: () => { toast.success("Plan updated"); refetch(); }, onError: () => toast.error("Failed to update plan"), }); const editPlanMutation = useMutation({ mutationFn: async () => { if (!editPlan) return; await api.patch(`/api/v1/plans/${editPlan.id}`, { name: editForm.name, type: editForm.type, speedDownMbps: parseInt(editForm.speedDown), speedUpMbps: parseInt(editForm.speedUp), monthlyPrice: parseFloat(editForm.price), description: editForm.description || undefined, }); }, onSuccess: () => { toast.success("Plan updated"); setEditPlan(null); refetch(); }, onError: () => toast.error("Failed to update plan"), }); function openEdit(p: Plan) { setEditPlan(p); setEditForm({ name: p.name, type: p.type, speedDown: String(p.speedDownMbps), speedUp: String(p.speedUpMbps), price: String(Number(p.monthlyPrice)), description: p.description ?? "", }); } function resetAdd() { setPlanName(""); setPlanType("POSTPAID"); setSpeedDown(""); setSpeedUp(""); setPrice(""); setDescription(""); } const plans = data ?? []; return (
Plans {isLoading ? ( Array.from({ length: 3 }).map((_, i) => ( {[1,2,3,4,5,6].map((j) => ( ))} )) ) : plans.length === 0 ? ( ) : ( plans.map((p) => ( )) )}
Name Type Speed Price Status Actions
{p.name} {p.type} {p.speedDownMbps}/{p.speedUpMbps} Mbps {formatCurrency(Number(p.monthlyPrice))} {p.isActive ? "Active" : "Inactive"}
{/* Edit Plan Modal */} setEditPlan(null)} title="Edit Plan" className="max-w-lg">
{ e.preventDefault(); editPlanMutation.mutate(); }} className="space-y-4"> setEditForm(f => ({ ...f, name: e.target.value }))} />
setEditForm(f => ({ ...f, speedDown: e.target.value }))} /> setEditForm(f => ({ ...f, speedUp: e.target.value }))} />
setEditForm(f => ({ ...f, price: e.target.value }))} /> setEditForm(f => ({ ...f, description: e.target.value }))} />
{/* Add Plan Modal */} { setShowAdd(false); resetAdd(); }} title="Add Plan" className="max-w-lg">
{ e.preventDefault(); addPlanMutation.mutate(); }} className="space-y-4"> setPlanName(e.target.value)} placeholder="e.g. Basic 10 Mbps" />
setSpeedDown(e.target.value)} /> setSpeedUp(e.target.value)} />
setPrice(e.target.value)} /> setDescription(e.target.value)} />
); } // ─── Sub-page: Users ────────────────────────────────────────────────────────── interface UserItem { id: string; firstName: string; lastName: string; email: string; phone?: string; isActive: boolean; roleAssignments?: { role: string }[]; } const ROLES = ["ADMIN", "STAFF", "COLLECTOR", "TECHNICIAN"]; function UsersSettings() { const qc = useQueryClient(); const [showAdd, setShowAdd] = useState(false); const [form, setForm] = useState({ firstName: "", lastName: "", email: "", password: "", phone: "", role: "STAFF" }); const { data, isLoading, refetch } = useQuery({ queryKey: ["users-settings"], queryFn: async () => { const res = await api.get("/api/v1/users"); return Array.isArray(res.data) ? res.data : []; }, }); const createUser = useMutation({ mutationFn: async () => { await api.post("/api/v1/users", { firstName: form.firstName, lastName: form.lastName, email: form.email, password: form.password, phone: form.phone || undefined, role: form.role, }); }, onSuccess: () => { toast.success("User created!"); setShowAdd(false); setForm({ firstName: "", lastName: "", email: "", password: "", phone: "", role: "STAFF" }); refetch(); }, onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed to create user"), }); const toggleActive = useMutation({ mutationFn: async ({ id, isActive }: { id: string; isActive: boolean }) => { await api.patch(`/api/v1/users/${id}`, { isActive: !isActive }); }, onSuccess: () => { toast.success("User updated"); refetch(); }, onError: (e: any) => toast.error(e.response?.data?.message ?? "Failed"), }); const users = data ?? []; return (
Users ({users.length}) {isLoading ? ( Array.from({ length: 3 }).map((_, i) => ( {[1,2,3,4,5].map(j => )} )) ) : users.length === 0 ? ( ) : users.map(u => { const role = u.roleAssignments?.[0]?.role ?? "—"; return ( ); })}
NameEmailRoleStatusActions
{u.firstName} {u.lastName}
{u.phone ?? ""}
{u.email} {role} {u.isActive ? "Active" : "Inactive"}
setShowAdd(false)} title="Add New User">
setForm(f => ({ ...f, firstName: e.target.value }))} /> setForm(f => ({ ...f, lastName: e.target.value }))} />
setForm(f => ({ ...f, email: e.target.value }))} /> setForm(f => ({ ...f, password: e.target.value }))} hint="Minimum 8 characters" /> setForm(f => ({ ...f, phone: e.target.value }))} />
); } // ─── Main Page ───────────────────────────────────────────────────────────────── const SUB_NAV = [ { key: "tenant", label: "Tenant", icon: Building2 }, { key: "billing", label: "Billing", icon: CreditCard }, { key: "areas", label: "Areas & Zones", icon: Map }, { key: "plans", label: "Plans", icon: Wifi }, { key: "users", label: "Users", icon: Users }, ] as const; type SubPage = typeof SUB_NAV[number]["key"]; export default function SettingsPage() { const [active, setActive] = useState("tenant"); return (

Settings

Manage your ISP configuration

{/* Left sub-nav */} {/* Content */}
{active === "tenant" && } {active === "billing" && } {active === "areas" && } {active === "plans" && } {active === "users" && }
); }