ff56cea4b8
- Introduced new migration for `facebook_accounts` and `facebook_scrape_logs` tables. - Updated the scraping logic to handle Facebook accounts, including success and failure tracking. - Implemented API endpoints for managing Facebook accounts and their scrape logs. - Enhanced user permissions to restrict access to Facebook account management to admins and super admins. - Added a dialog component for displaying and managing Facebook accounts in the UI. - Updated lead fetching logic to include user role checks for assignment and access.
103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
|
import { ShieldAlert, RefreshCw } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
interface Account {
|
|
id: string
|
|
label: string
|
|
is_active: boolean
|
|
last_scrape_at: string | null
|
|
last_success_at: string | null
|
|
last_error_at: string | null
|
|
consecutive_failures: number
|
|
flagged: boolean
|
|
flagged_reason: string | null
|
|
last_leads_found: number
|
|
last_success: boolean | null
|
|
}
|
|
|
|
export function FacebookAccountsDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (v: boolean) => void }) {
|
|
const [accounts, setAccounts] = useState<Account[]>([])
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (open) load()
|
|
}, [open])
|
|
|
|
async function load() {
|
|
setLoading(true)
|
|
try {
|
|
const res = await fetch("/api/settings/facebook/accounts")
|
|
if (res.ok) {
|
|
setAccounts(await res.json())
|
|
}
|
|
} catch {}
|
|
setLoading(false)
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-2xl">
|
|
<DialogHeader className="flex flex-row items-center justify-between">
|
|
<div>
|
|
<DialogTitle>Facebook Accounts</DialogTitle>
|
|
<DialogDescription>
|
|
Status of Facebook profiles used for lead scraping
|
|
</DialogDescription>
|
|
</div>
|
|
<Button variant="ghost" size="icon" onClick={load} disabled={loading}>
|
|
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
|
|
</Button>
|
|
</DialogHeader>
|
|
|
|
{loading ? (
|
|
<div className="text-center py-8 text-muted-foreground">Loading...</div>
|
|
) : accounts.length === 0 ? (
|
|
<div className="text-center py-8 text-muted-foreground">No accounts configured</div>
|
|
) : (
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Label</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Last Scrape</TableHead>
|
|
<TableHead>Leads</TableHead>
|
|
<TableHead>Failures</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{accounts.map((a) => (
|
|
<TableRow key={a.id}>
|
|
<TableCell className="font-medium">{a.label}</TableCell>
|
|
<TableCell>
|
|
{a.flagged ? (
|
|
<Badge variant="destructive" className="gap-1">
|
|
<ShieldAlert className="h-3 w-3" />
|
|
{a.flagged_reason || "Flagged"}
|
|
</Badge>
|
|
) : a.is_active ? (
|
|
<Badge variant="default" className="bg-green-600">Active</Badge>
|
|
) : (
|
|
<Badge variant="secondary">Disabled</Badge>
|
|
)}
|
|
</TableCell>
|
|
<TableCell className="text-sm text-muted-foreground">
|
|
{a.last_scrape_at ? new Date(a.last_scrape_at).toLocaleString() : "Never"}
|
|
</TableCell>
|
|
<TableCell>{a.last_leads_found}</TableCell>
|
|
<TableCell>{a.consecutive_failures}</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|