feat: add Review Management module and UI layout fixes

This commit is contained in:
2026-03-07 11:55:18 +05:30
commit 1da66c1be5
226 changed files with 39160 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
import { Link } from 'react-router-dom';
import {
UserCheck,
Flag,
Wallet,
AlertTriangle,
ArrowRight
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { ActionItem } from '@/types/dashboard';
import { formatCurrency } from '@/data/mockData';
interface ActionItemsPanelProps {
items: ActionItem[];
}
const iconMap = {
kyc: UserCheck,
flagged: Flag,
payout: Wallet,
stripe: AlertTriangle,
};
const colorMap = {
kyc: 'text-warning bg-warning/10',
flagged: 'text-error bg-error/10',
payout: 'text-success bg-success/10',
stripe: 'text-warning bg-warning/10',
};
export function ActionItemsPanel({ items }: ActionItemsPanelProps) {
return (
<div className="neu-card p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-lg font-bold text-foreground">Pending Actions</h2>
<p className="text-sm text-muted-foreground">Items requiring your attention</p>
</div>
<span className="h-8 px-3 flex items-center justify-center rounded-full bg-error/10 text-error text-sm font-semibold">
{items.length} urgent
</span>
</div>
<div className="space-y-3">
{items.map((item) => {
const Icon = iconMap[item.type];
const colorClass = colorMap[item.type];
return (
<Link
key={item.id}
to={item.href}
className={cn(
"flex items-center gap-4 p-4 rounded-xl",
"bg-secondary/50 hover:bg-secondary",
"transition-all duration-200 group"
)}
>
<div className={cn(
"h-12 w-12 rounded-xl flex items-center justify-center",
colorClass
)}>
<Icon className="h-6 w-6" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className={cn(
"inline-flex h-6 min-w-6 px-2 items-center justify-center rounded-md text-xs font-bold",
item.priority === 'high' ? "bg-error text-error-foreground" : "bg-warning text-warning-foreground"
)}>
{item.type === 'payout' ? formatCurrency(item.count) : item.count}
</span>
<h3 className="font-semibold text-foreground">{item.title}</h3>
</div>
<p className="text-sm text-muted-foreground truncate">{item.description}</p>
</div>
<ArrowRight className="h-5 w-5 text-muted-foreground opacity-0 group-hover:opacity-100 group-hover:translate-x-1 transition-all duration-200" />
</Link>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,85 @@
import {
Users,
Calendar,
Wallet,
User,
CheckCircle2,
Clock,
AlertTriangle,
XCircle
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { ActivityItem } from '@/types/dashboard';
import { formatRelativeTime } from '@/data/mockData';
interface ActivityFeedProps {
items: ActivityItem[];
}
const typeIconMap = {
partner: Users,
event: Calendar,
payout: Wallet,
user: User,
};
const statusIconMap = {
success: CheckCircle2,
pending: Clock,
warning: AlertTriangle,
error: XCircle,
};
const statusColorMap = {
success: 'text-success',
pending: 'text-warning',
warning: 'text-warning',
error: 'text-error',
};
export function ActivityFeed({ items }: ActivityFeedProps) {
return (
<div className="neu-card p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-lg font-bold text-foreground">Recent Activity</h2>
<p className="text-sm text-muted-foreground">Latest platform updates</p>
</div>
</div>
<div className="space-y-4">
{items.map((item, index) => {
const TypeIcon = typeIconMap[item.type];
const StatusIcon = statusIconMap[item.status];
const statusColor = statusColorMap[item.status];
return (
<div
key={item.id}
className={cn(
"flex items-start gap-4 pb-4",
index !== items.length - 1 && "border-b border-border/50"
)}
>
<div className="h-10 w-10 rounded-xl bg-secondary shadow-neu-inset-sm flex items-center justify-center">
<TypeIcon className="h-5 w-5 text-muted-foreground" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h3 className="font-medium text-foreground">{item.title}</h3>
<StatusIcon className={cn("h-4 w-4", statusColor)} />
</div>
<p className="text-sm text-muted-foreground truncate">{item.description}</p>
</div>
<span className="text-xs text-muted-foreground whitespace-nowrap">
{formatRelativeTime(item.timestamp)}
</span>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,64 @@
import { LucideIcon, TrendingUp, TrendingDown } from 'lucide-react';
import { cn } from '@/lib/utils';
interface DashboardMetricCardProps {
title: string;
value: string;
subtitle?: string;
icon: LucideIcon;
trend?: {
value: number;
label: string;
positive?: boolean;
};
iconColor?: string;
}
export function DashboardMetricCard({
title,
value,
subtitle,
icon: Icon,
trend,
iconColor = "text-accent"
}: DashboardMetricCardProps) {
return (
<div className="neu-card neu-card-hover p-6 group">
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-muted-foreground mb-1">{title}</p>
<p className="text-3xl font-bold text-foreground mb-2">{value}</p>
{trend && (
<div className="flex items-center gap-1.5">
{trend.positive !== false ? (
<TrendingUp className="h-4 w-4 text-success" />
) : (
<TrendingDown className="h-4 w-4 text-error" />
)}
<span className={cn(
"text-sm font-medium",
trend.positive !== false ? "text-success" : "text-error"
)}>
{trend.positive !== false ? '+' : ''}{trend.value}%
</span>
<span className="text-sm text-muted-foreground">{trend.label}</span>
</div>
)}
{subtitle && !trend && (
<p className="text-sm text-muted-foreground">{subtitle}</p>
)}
</div>
<div className={cn(
"h-14 w-14 rounded-xl flex items-center justify-center",
"bg-secondary shadow-neu-inset-sm",
"group-hover:shadow-neu-inset transition-shadow duration-200"
)}>
<Icon className={cn("h-7 w-7", iconColor)} />
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,76 @@
import { RevenueDataPoint } from '@/types/dashboard';
import { formatCurrency } from '@/data/mockData';
interface RevenueChartProps {
data: RevenueDataPoint[];
}
export function RevenueChart({ data }: RevenueChartProps) {
const maxValue = Math.max(...data.map(d => Math.max(d.revenue, d.payouts)));
return (
<div className="neu-card p-6">
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-lg font-bold text-foreground">Revenue vs Payouts</h2>
<p className="text-sm text-muted-foreground">Last 7 days comparison</p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<span className="h-3 w-3 rounded-sm bg-accent" />
<span className="text-sm text-muted-foreground">Revenue</span>
</div>
<div className="flex items-center gap-2">
<span className="h-3 w-3 rounded-sm bg-success" />
<span className="text-sm text-muted-foreground">Payouts</span>
</div>
</div>
</div>
{/* Chart Area */}
<div className="h-64 flex items-end gap-4 pt-8 pb-4">
{data.map((point, index) => (
<div key={index} className="flex-1 flex flex-col items-center gap-2">
<div className="w-full flex items-end justify-center gap-1 h-48">
{/* Revenue Bar */}
<div
className="w-5 bg-accent rounded-t-md transition-all duration-300 hover:bg-ocean-blue"
style={{
height: `${(point.revenue / maxValue) * 100}%`,
minHeight: '8px'
}}
title={formatCurrency(point.revenue)}
/>
{/* Payout Bar */}
<div
className="w-5 bg-success rounded-t-md transition-all duration-300 hover:opacity-80"
style={{
height: `${(point.payouts / maxValue) * 100}%`,
minHeight: '8px'
}}
title={formatCurrency(point.payouts)}
/>
</div>
<span className="text-xs font-medium text-muted-foreground">{point.day}</span>
</div>
))}
</div>
{/* Summary */}
<div className="grid grid-cols-2 gap-4 pt-4 border-t border-border/50">
<div className="text-center">
<p className="text-2xl font-bold text-foreground">
{formatCurrency(data.reduce((sum, d) => sum + d.revenue, 0))}
</p>
<p className="text-sm text-muted-foreground">Total Revenue</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-foreground">
{formatCurrency(data.reduce((sum, d) => sum + d.payouts, 0))}
</p>
<p className="text-sm text-muted-foreground">Total Payouts</p>
</div>
</div>
</div>
);
}