181 lines
5.9 KiB
TypeScript

"use client";
import { useState } from "react";
import { ArrowTrendingUpIcon } from "@heroicons/react/24/outline";
import { cn } from "@/lib/utils";
import { DashboardActivityItem } from "./DashboardActivityItem";
import {
filterActivities,
ACTIVITY_FILTERS,
getActivityNavigationPath,
isActivityClickable,
} from "../utils/dashboard.utils";
import type { Activity } from "@customer-portal/domain/billing";
import type { ActivityFilter } from "@customer-portal/domain/billing";
export interface ActivityFeedProps {
activities: Activity[];
onItemClick?: (activity: Activity) => void;
className?: string;
maxItems?: number;
showFilter?: boolean;
loading?: boolean;
error?: string | null;
}
export function ActivityFeed({
activities,
onItemClick,
className,
maxItems = 10,
showFilter = true,
loading = false,
error = null,
}: ActivityFeedProps) {
const [filter, setFilter] = useState<ActivityFilter>("all");
const filteredActivities = filterActivities(activities, filter);
const displayActivities = filteredActivities.slice(0, maxItems);
const handleActivityClick = (activity: Activity) => {
if (onItemClick) {
onItemClick(activity);
} else if (isActivityClickable(activity)) {
const path = getActivityNavigationPath(activity);
if (path) {
window.location.href = path;
}
}
};
if (loading) {
return (
<div
className={cn(
"bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden",
className
)}
>
<div className="px-6 py-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<div className="h-6 bg-gray-200 rounded animate-pulse w-32" />
{showFilter && (
<div className="flex items-center space-x-1 bg-gray-100 rounded-lg p-1">
{ACTIVITY_FILTERS.map((_, index) => (
<div key={index} className="h-6 w-12 bg-gray-200 rounded animate-pulse" />
))}
</div>
)}
</div>
</div>
<div className="p-6 space-y-4">
{Array.from({ length: 3 }).map((_, index) => (
<div key={index} className="flex items-start space-x-4">
<div className="w-10 h-10 rounded-full bg-gray-200 animate-pulse" />
<div className="flex-1 space-y-2">
<div className="h-4 bg-gray-200 rounded animate-pulse w-3/4" />
<div className="h-3 bg-gray-200 rounded animate-pulse w-1/2" />
<div className="h-3 bg-gray-200 rounded animate-pulse w-1/4" />
</div>
</div>
))}
</div>
</div>
);
}
if (error) {
return (
<div
className={cn(
"bg-white rounded-2xl shadow-lg border border-red-200 overflow-hidden",
className
)}
>
<div className="px-6 py-4 border-b border-red-100">
<h3 className="text-lg font-semibold text-gray-900">Recent Activity</h3>
</div>
<div className="p-6">
<div className="text-center py-8">
<div className="text-red-600 text-sm">{error}</div>
</div>
</div>
</div>
);
}
return (
<div
className={cn(
"bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden",
className
)}
>
<div className="px-6 py-4 border-b border-gray-100">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">Recent Activity</h3>
{showFilter && (
<div className="flex items-center space-x-1 bg-gray-100 rounded-lg p-1">
{ACTIVITY_FILTERS.map(filterOption => (
<button
key={filterOption.key}
onClick={() => setFilter(filterOption.key)}
className={cn(
"px-2.5 py-1 text-xs rounded-md font-medium transition-all duration-200",
filter === filterOption.key
? "bg-white text-gray-900 shadow"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"
)}
>
{filterOption.label}
</button>
))}
</div>
)}
</div>
</div>
<div className="p-6 max-h-[360px] overflow-y-auto">
{displayActivities.length > 0 ? (
<div className="space-y-4">
{displayActivities.map(activity => {
const clickable = isActivityClickable(activity);
return (
<DashboardActivityItem
key={activity.id}
id={activity.id}
type={activity.type}
title={activity.title ?? ""}
description={activity.description ?? ""}
date={activity.date}
onClick={clickable ? () => handleActivityClick(activity) : undefined}
/>
);
})}
</div>
) : (
<div className="text-center py-12">
<ArrowTrendingUpIcon className="mx-auto h-12 w-12 text-gray-400" />
<h3 className="mt-2 text-sm font-medium text-gray-900">
{filter === "all" ? "No recent activity" : `No ${filter} activity`}
</h3>
<p className="mt-1 text-sm text-gray-500">
{filter === "all"
? "Your account activity will appear here."
: `Your ${filter} activity will appear here.`}
</p>
</div>
)}
</div>
{filteredActivities.length > maxItems && (
<div className="px-6 py-3 border-t border-gray-100 bg-gray-50">
<p className="text-xs text-gray-500 text-center">
Showing {maxItems} of {filteredActivities.length} activities
</p>
</div>
)}
</div>
);
}