feat: add Review Management module and UI layout fixes
This commit is contained in:
86
src/components/dashboard/ActionItemsPanel.tsx
Normal file
86
src/components/dashboard/ActionItemsPanel.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
85
src/components/dashboard/ActivityFeed.tsx
Normal file
85
src/components/dashboard/ActivityFeed.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
64
src/components/dashboard/DashboardMetricCard.tsx
Normal file
64
src/components/dashboard/DashboardMetricCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
76
src/components/dashboard/RevenueChart.tsx
Normal file
76
src/components/dashboard/RevenueChart.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user