262 lines
17 KiB
Text
262 lines
17 KiB
Text
<TabsContent value="dashboard" className="space-y-4">
|
|
{/* Stats Overview */}
|
|
{stats && (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Total Revenue</CardTitle>
|
|
<span className="text-xs text-muted-foreground">$</span>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">${stats.monthlyRevenue?.toLocaleString() || '0'}</div>
|
|
<p className="text-xs text-muted-foreground">{stats.revenueGrowth ? `+${stats.revenueGrowth}% from last month` : 'This month'}</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Active Subscriptions</CardTitle>
|
|
<CheckCircle className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{stats.activeSubscriptions || 0}</div>
|
|
<p className="text-xs text-muted-foreground">{stats.subscriptionGrowth ? `+${stats.subscriptionGrowth} this week` : 'Current active'}</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Companies</CardTitle>
|
|
<div className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{stats.totalCompanies || 0}</div>
|
|
<p className="text-xs text-muted-foreground">Platform total</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">New (Month)</CardTitle>
|
|
<Plus className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">+{stats.newCompaniesThisMonth || 0}</div>
|
|
<p className="text-xs text-muted-foreground">Since start of month</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
|
<Card className="col-span-4">
|
|
<CardHeader>
|
|
<CardTitle>Empresas pendentes</CardTitle>
|
|
<CardDescription>Aprovação e verificação de empresas.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Empresa</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead className="text-right">Ações</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{companies.slice(0, 5).map((company) => (
|
|
<TableRow key={company.id}>
|
|
<TableCell className="font-medium">{company.name}</TableCell>
|
|
<TableCell>
|
|
{company.verified ? <Badge className="bg-green-500">Verificada</Badge> : <Badge variant="secondary">Pendente</Badge>}
|
|
</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button size="sm" variant="ghost" onClick={() => handleApproveCompany(company.id)}>
|
|
<CheckCircle className="h-4 w-4" />
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
<Card className="col-span-3">
|
|
<CardHeader>
|
|
<CardTitle>Auditoria Recente</CardTitle>
|
|
<CardDescription>Últimos acessos.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-8">
|
|
{audits.slice(0, 5).map((audit) => (
|
|
<div key={audit.id} className="flex items-center">
|
|
<div className="ml-4 space-y-1">
|
|
<p className="text-sm font-medium leading-none">{audit.identifier}</p>
|
|
<p className="text-xs text-muted-foreground">{auditDateFormatter.format(new Date(audit.createdAt))}</p>
|
|
</div>
|
|
<div className="ml-auto font-medium text-xs text-muted-foreground">{audit.roles}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="plans" className="space-y-4">
|
|
<div className="flex justify-end">
|
|
<Button onClick={() => { setEditingPlanId(null); setPlanForm({ name: "", description: "", monthlyPrice: 0, yearlyPrice: 0, features: [] }); setIsPlanDialogOpen(true) }}>
|
|
<Plus className="mr-2 h-4 w-4" /> Create Plan
|
|
</Button>
|
|
</div>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Plans Management</CardTitle>
|
|
<CardDescription>Configure subscription plans.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Name</TableHead>
|
|
<TableHead>Monthly</TableHead>
|
|
<TableHead>Yearly</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{plans.map((plan) => (
|
|
<TableRow key={plan.id}>
|
|
<TableCell className="font-medium">{plan.name}</TableCell>
|
|
<TableCell>${plan.monthlyPrice}</TableCell>
|
|
<TableCell>${plan.yearlyPrice}</TableCell>
|
|
<TableCell className="text-right space-x-2">
|
|
<Button size="sm" variant="outline" onClick={() => { setEditingPlanId(plan.id); setPlanForm({ ...plan }); setIsPlanDialogOpen(true) }}>Edit</Button>
|
|
<Button size="sm" variant="destructive" onClick={() => handleDeletePlan(plan.id)}>Delete</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Dialog open={isPlanDialogOpen} onOpenChange={setIsPlanDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{editingPlanId ? 'Edit Plan' : 'Create Plan'}</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="grid gap-4 py-4">
|
|
<div className="grid gap-2">
|
|
<Label>Name</Label>
|
|
<Input value={planForm.name} onChange={(e) => setPlanForm({ ...planForm, name: e.target.value })} />
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Description</Label>
|
|
<Input value={planForm.description} onChange={(e) => setPlanForm({ ...planForm, description: e.target.value })} />
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="grid gap-2">
|
|
<Label>Monthly Price</Label>
|
|
<Input type="number" value={planForm.monthlyPrice} onChange={(e) => setPlanForm({ ...planForm, monthlyPrice: e.target.value })} />
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Yearly Price</Label>
|
|
<Input type="number" value={planForm.yearlyPrice} onChange={(e) => setPlanForm({ ...planForm, yearlyPrice: e.target.value })} />
|
|
</div>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>Features (comma separated)</Label>
|
|
<Textarea value={Array.isArray(planForm.features) ? planForm.features.join(', ') : planForm.features} onChange={(e) => setPlanForm({ ...planForm, features: e.target.value })} />
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setIsPlanDialogOpen(false)}>Cancel</Button>
|
|
<Button onClick={handleSavePlan}>Save</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="stripe" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Stripe Integration</CardTitle>
|
|
<CardDescription>Manage subscriptions and payments directly in Stripe Dashboard.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="p-4 border rounded bg-muted/20">
|
|
<p className="text-sm">
|
|
For security and advanced management (refunds, disputes, tax settings), please use the official Stripe Dashboard.
|
|
</p>
|
|
<div className="mt-4">
|
|
<a href="https://dashboard.stripe.com" target="_blank" rel="noreferrer">
|
|
<Button variant="outline">
|
|
Open Stripe Dashboard <ExternalLink className="ml-2 h-4 w-4" />
|
|
</Button>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="system" className="space-y-4">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>System & Caching</CardTitle>
|
|
<CardDescription>Maintenance tasks.</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-between p-4 border rounded">
|
|
<div>
|
|
<p className="font-medium">Cloudflare Cache</p>
|
|
<p className="text-sm text-muted-foreground">Purge all cached files from the edge.</p>
|
|
</div>
|
|
<Button variant="outline" onClick={handlePurgeCache}>Purge Cache</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Tags Management</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{/* Reusing existing Tags Table logic here if desired, or keep it in a sub-section */}
|
|
<div className="flex flex-col md:flex-row gap-4 mb-4">
|
|
<Input placeholder="New Tag" value={tagForm.name} onChange={(e) => setTagForm({ ...tagForm, name: e.target.value })} />
|
|
<Select value={tagForm.category} onValueChange={(val: any) => setTagForm({ ...tagForm, category: val })}>
|
|
<SelectTrigger className="w-40"><SelectValue placeholder="Category" /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="area">Area</SelectItem>
|
|
<SelectItem value="level">Level</SelectItem>
|
|
<SelectItem value="stack">Stack</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<Button onClick={handleCreateTag} disabled={creatingTag}><Plus className="mr-2 h-4 w-4" /> Add</Button>
|
|
</div>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Tag</TableHead>
|
|
<TableHead>Category</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead className="text-right">Action</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{tags.map((tag) => (
|
|
<TableRow key={tag.id}>
|
|
<TableCell>{tag.name}</TableCell>
|
|
<TableCell>{tag.category}</TableCell>
|
|
<TableCell>{tag.active ? <Badge className="bg-green-500">Active</Badge> : <Badge variant="outline">Inactive</Badge>}</TableCell>
|
|
<TableCell className="text-right">
|
|
<Button size="sm" variant="ghost" onClick={() => handleToggleTag(tag)}>Toggle</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</TabsContent>
|
|
</Tabs>
|
|
<ConfirmModal
|