feat(users): implement inspector tabs and server actions

This commit is contained in:
CycroftX
2026-02-09 21:46:55 +05:30
parent df4d881437
commit 389ebdeb5d
8 changed files with 1049 additions and 121 deletions

View File

@@ -59,6 +59,11 @@ import {
} from '../data/mockUserCrmData';
import { formatCurrency } from '@/data/mockData';
import { ActionButtons } from './ActionButtons';
import { OverviewTab } from './tabs/OverviewTab';
import { BookingsTab } from './tabs/BookingsTab';
import { SecurityTab } from './tabs/SecurityTab';
import { SupportTab } from './tabs/SupportTab';
import { AuditTab } from './tabs/AuditTab';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
@@ -78,7 +83,6 @@ export function UserInspectorSheet({
onSendNotification,
}: UserInspectorSheetProps) {
const [activeTab, setActiveTab] = useState('overview');
const [noteContent, setNoteContent] = useState('');
if (!user) return null;
@@ -215,138 +219,37 @@ export function UserInspectorSheet({
<TabsList className="w-full justify-start h-9 bg-transparent p-0">
<TabsTrigger value="overview" className="h-9 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 text-xs">Overview</TabsTrigger>
<TabsTrigger value="orders" className="h-9 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 text-xs">Orders</TabsTrigger>
<TabsTrigger value="admin" className="h-9 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 text-xs">Admin Notes</TabsTrigger>
<TabsTrigger value="security" className="h-9 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 text-xs">Security</TabsTrigger>
<TabsTrigger value="support" className="h-9 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 text-xs">Support</TabsTrigger>
<TabsTrigger value="admin" className="h-9 rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent px-4 text-xs">Audit</TabsTrigger>
</TabsList>
</div>
<ScrollArea className="flex-1 bg-secondary/5">
<div className="p-4 space-y-6">
<div className="p-4">
{/* Tab 1: Overview */}
<TabsContent value="overview" className="m-0 space-y-6">
{/* Contact Info */}
<div>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">Contact Information</h3>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-3">
<Mail className="h-3.5 w-3.5 text-primary/60" />
<span className="text-foreground">{user.email}</span>
</div>
<div className="flex items-center gap-3">
<Phone className="h-3.5 w-3.5 text-primary/60" />
<span className="text-foreground">{user.countryCode} {user.phone}</span>
</div>
{user.lastDevice && (
<div className="flex items-center gap-3">
<MapPin className="h-3.5 w-3.5 text-primary/60" />
<span className="text-foreground">{user.lastDevice.location}</span>
</div>
)}
</div>
</div>
<Separator className="border-dashed" />
{/* Last Activity */}
<div>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">Last Activity</h3>
<div className="flex items-start gap-3 p-3 bg-white border border-border/50 rounded-lg shadow-sm">
<div className="mt-0.5">
<Clock className="h-4 w-4 text-blue-500" />
</div>
<div>
<p className="text-sm font-medium">Scanned at <span className="text-foreground font-semibold">Tech Summit 2026</span></p>
<p className="text-xs text-muted-foreground mt-0.5">2 hours ago Verified by Staff</p>
</div>
</div>
</div>
<Separator className="border-dashed" />
{/* Tags */}
<div>
<h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">Tags</h3>
<div className="flex flex-wrap gap-2">
{user.tags.map((tag) => (
<Badge key={tag.id} variant="outline" className={cn("rounded-md px-2 py-0.5 font-normal", tag.color)}>
{tag.name}
</Badge>
))}
<Button variant="outline" size="sm" className="h-6 rounded-md px-2 text-xs border-dashed gap-1 text-muted-foreground hover:text-foreground">
<Plus className="h-3 w-3" /> Add
</Button>
</div>
</div>
<TabsContent value="overview" className="m-0">
<OverviewTab user={user} notes={notes.length > 0 ? notes[0].content : ''} />
</TabsContent>
{/* Tab 2: Orders */}
<TabsContent value="orders" className="m-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
<TableHead className="h-8 text-xs font-medium">Event</TableHead>
<TableHead className="h-8 text-xs font-medium text-right">Date</TableHead>
<TableHead className="h-8 text-xs font-medium text-right">Amount</TableHead>
<TableHead className="h-8 text-xs font-medium text-right">Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{bookings.slice(0, 5).map((booking) => (
<TableRow key={booking.id} className="hover:bg-transparent border-0">
<TableCell className="py-2 text-sm font-medium">
{booking.eventName}
<div className="text-[10px] text-muted-foreground font-normal">{booking.ticketType} x{booking.quantity}</div>
</TableCell>
<TableCell className="py-2 text-xs text-right text-muted-foreground">
{new Date(booking.eventDate).toLocaleDateString('en-IN', { day: 'numeric', month: 'short' })}
</TableCell>
<TableCell className="py-2 text-xs text-right font-medium">
{formatCurrency(booking.amount)}
</TableCell>
<TableCell className="py-2 text-right">
<Badge variant="outline" className="text-[10px] h-5 px-1.5 font-normal">
{booking.status}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{bookings.length === 0 && (
<div className="text-center py-8 text-xs text-muted-foreground">No orders found.</div>
)}
<BookingsTab bookings={bookings} />
</TabsContent>
{/* Tab 3: Admin Notes */}
<TabsContent value="admin" className="m-0 space-y-4">
<div className="p-3 bg-amber-50 border border-amber-200 rounded-lg space-y-2">
<div className="flex items-center gap-2 mb-2">
<AlertTriangle className="h-3.5 w-3.5 text-amber-600" />
<span className="text-xs font-bold text-amber-800 uppercase tracking-wide">Internal Notes</span>
</div>
<Textarea
className="min-h-[100px] border- amber-200/50 bg-white/50 focus-visible:ring-amber-500/30 text-sm resize-none"
placeholder="Add private notes about this user..."
value={noteContent}
onChange={(e) => setNoteContent(e.target.value)}
/>
<div className="flex justify-end">
<Button size="sm" className="h-7 text-xs bg-amber-600 hover:bg-amber-700 text-white border-none shadow-none">
Save Note
</Button>
</div>
</div>
{/* Tab 3: Security */}
<TabsContent value="security" className="m-0">
<SecurityTab user={user} />
</TabsContent>
<div className="space-y-3">
{notes.map((note) => (
<div key={note.id} className="relative pl-4 border-l-2 border-border/50 py-1">
<p className="text-sm text-foreground">{note.content}</p>
<div className="flex items-center gap-2 mt-1">
<span className="text-[10px] font-medium text-muted-foreground">{note.authorName}</span>
<span className="text-[10px] text-muted-foreground/60"> {new Date(note.createdAt).toLocaleDateString()}</span>
</div>
</div>
))}
</div>
{/* Tab 4: Support */}
<TabsContent value="support" className="m-0">
<SupportTab user={user} />
</TabsContent>
{/* Tab 5: Audit (Admin Notes / Activity) */}
<TabsContent value="admin" className="m-0">
<AuditTab userId={user.id} />
</TabsContent>
</div>
</ScrollArea>