Files
eventify_command_center/src/features/users/components/UserInspectorSheet.tsx

261 lines
13 KiB
TypeScript

import { useState } from 'react';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Textarea } from '@/components/ui/textarea';
import { Separator } from '@/components/ui/separator';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import {
User,
Mail,
Phone,
MapPin,
Shield,
Ticket,
CheckCircle2,
KeyRound,
Eye,
MessageSquare,
Ban,
Copy,
MoreHorizontal,
MoreVertical,
Clock,
AlertTriangle,
Plus,
Tag,
} from 'lucide-react';
import type { User as UserType } from '@/lib/types/user';
import {
getUserBookings,
getUserNotes,
} 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';
interface UserInspectorSheetProps {
user: UserType | null;
open: boolean;
onOpenChange: (open: boolean) => void;
onEditUser: (user: UserType) => void;
onSendNotification: (user: UserType) => void;
}
export function UserInspectorSheet({
user,
open,
onOpenChange,
onEditUser,
onSendNotification,
}: UserInspectorSheetProps) {
const [activeTab, setActiveTab] = useState('overview');
if (!user) return null;
// Fetch related data
const bookings = getUserBookings(user.id);
const notes = getUserNotes(user.id);
// Derived Metrics
const successBookings = bookings.filter(b => b.status === 'Attended' || b.status === 'Confirmed').length;
const cancelledBookings = bookings.filter(b => b.status === 'Cancelled' || b.status === 'Refunded').length;
const isHighRisk = user.refundRate > 5;
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
toast.success('Copied to clipboard');
};
const handleAction = (label: string) => {
toast.success(`${label} action triggered`);
};
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-[500px] sm:w-[600px] p-0 border-l border-border/50 shadow-2xl overflow-hidden flex flex-col">
{/* Section A: The Header (Compact) */}
<div className="bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 p-4 border-b border-border/50">
<div className="flex items-start justify-between gap-4">
<div className="flex items-center gap-4">
<Avatar className="h-14 w-14 border-2 border-background shadow-sm">
<AvatarImage src={user.avatarUrl} alt={user.name} />
<AvatarFallback className="text-lg bg-primary/10 text-primary font-bold">
{user.name.charAt(0)}
</AvatarFallback>
</Avatar>
<div>
<div className="flex items-center gap-2">
<h2 className="text-lg font-bold leading-none">{user.name}</h2>
{user.isVerified && (
<CheckCircle2 className="h-4 w-4 text-blue-500 fill-blue-100" />
)}
</div>
<div className="flex items-center gap-2 mt-1.5 text-xs text-muted-foreground font-mono">
<span
className="flex items-center gap-1 cursor-pointer hover:text-foreground transition-colors"
onClick={() => copyToClipboard(user.id)}
title="Click to copy ID"
>
{user.id} <Copy className="h-3 w-3" />
</span>
<span></span>
<span>Joined {new Date(user.createdAt).toLocaleDateString('en-IN', { month: 'short', year: 'numeric' })}</span>
<Badge variant="secondary" className="h-5 px-1.5 text-[10px] font-normal ml-1">
{user.role}
</Badge>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Badge variant={user.status === 'Active' ? 'default' : 'destructive'} className="rounded-full">
{user.status}
</Badge>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onEditUser(user)}>Edit Profile</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleAction('Archive')}>Archive User</DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => handleAction('Delete')}>Delete User</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
{/* Section B: Key Metrics Grid */}
<div className="grid grid-cols-4 border-b border-border/50 divide-x divide-border/50 bg-secondary/10">
<div className="p-3 text-center">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium mb-0.5">LTV</p>
<p className={cn("text-sm font-bold", user.totalSpent > 50000 && "text-amber-600")}>
{formatCurrency(user.totalSpent)}
</p>
</div>
<div className="p-3 text-center">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium mb-0.5">Bookings</p>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<p className="text-sm font-bold cursor-help underline decoration-dotted underline-offset-4">
{user.bookingsCount}
</p>
</TooltipTrigger>
<TooltipContent>
<p className="text-xs">
<span className="text-emerald-500 font-medium">{successBookings} Success</span> <span className="text-red-500 font-medium">{cancelledBookings} Cancelled</span>
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="p-3 text-center">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium mb-0.5">Avg. Ticket</p>
<p className="text-sm font-bold text-foreground">
{formatCurrency(user.averageOrderValue)}
</p>
</div>
<div className="p-3 text-center">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium mb-0.5">Refund Risk</p>
<Badge
variant="outline"
className={cn("h-5 text-[10px] px-1.5", isHighRisk ? "bg-red-50 text-red-600 border-red-200" : "bg-emerald-50 text-emerald-600 border-emerald-200")}
>
{user.refundRate}% {isHighRisk ? 'High' : 'Low'}
</Badge>
</div>
</div>
{/* Section C: Action Toolbar */}
<div className="px-4 py-2 border-b border-border/50 bg-background flex items-center justify-between">
<ActionButtons
userId={user.id}
userName={user.name}
onSuspend={() => handleAction('Suspend')}
onSendNotification={() => onSendNotification(user)}
/>
</div>
{/* Section D: Tabbed Content */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col min-h-0">
<div className="px-4 border-b border-border/50">
<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="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">
{/* Tab 1: Overview */}
<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">
<BookingsTab bookings={bookings} />
</TabsContent>
{/* Tab 3: Security */}
<TabsContent value="security" className="m-0">
<SecurityTab user={user} />
</TabsContent>
{/* 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>
</Tabs>
</SheetContent>
</Sheet>
);
}