- Refactored DashboardView to integrate new TaskList, QuickStats, and ActivityFeed components for enhanced task management and user engagement. - Updated global styles in tokens.css to standardize color usage and improve accessibility across the application. - Adjusted color tokens for better visual coherence, including updates to brand and semantic colors. - Enhanced loading states and skeleton screens for a smoother user experience during data fetching.
176 lines
4.4 KiB
TypeScript
176 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { ChevronRightIcon } from "@heroicons/react/24/outline";
|
|
import { cn } from "@/lib/utils";
|
|
import { Button } from "@/components/atoms/button";
|
|
|
|
export type TaskTone = "critical" | "warning" | "info" | "neutral";
|
|
|
|
export interface TaskCardProps {
|
|
/** Unique identifier for the task */
|
|
id: string;
|
|
/** Icon component to display */
|
|
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
/** Task title */
|
|
title: string;
|
|
/** Task description */
|
|
description: string;
|
|
/** Action button label */
|
|
actionLabel: string;
|
|
/** Link destination for the card click (navigates to detail page) */
|
|
detailHref?: string;
|
|
/** Click handler for the action button */
|
|
onAction?: () => void;
|
|
/** Visual tone based on priority */
|
|
tone?: TaskTone;
|
|
/** Loading state for the action button */
|
|
isLoading?: boolean;
|
|
/** Loading text for the action button */
|
|
loadingText?: string;
|
|
/** Additional className */
|
|
className?: string;
|
|
}
|
|
|
|
const toneStyles: Record<
|
|
TaskTone,
|
|
{
|
|
card: string;
|
|
border: string;
|
|
iconBg: string;
|
|
iconColor: string;
|
|
buttonVariant: "default" | "outline";
|
|
}
|
|
> = {
|
|
critical: {
|
|
card: "bg-error/5 hover:bg-error/10",
|
|
border: "border-l-error",
|
|
iconBg: "bg-error/15",
|
|
iconColor: "text-error",
|
|
buttonVariant: "default",
|
|
},
|
|
warning: {
|
|
card: "bg-warning/5 hover:bg-warning/10",
|
|
border: "border-l-warning",
|
|
iconBg: "bg-warning/15",
|
|
iconColor: "text-warning",
|
|
buttonVariant: "outline",
|
|
},
|
|
info: {
|
|
card: "bg-info/5 hover:bg-info/10",
|
|
border: "border-l-info",
|
|
iconBg: "bg-info/15",
|
|
iconColor: "text-info",
|
|
buttonVariant: "outline",
|
|
},
|
|
neutral: {
|
|
card: "bg-primary/5 hover:bg-primary/10",
|
|
border: "border-l-primary",
|
|
iconBg: "bg-primary/15",
|
|
iconColor: "text-primary",
|
|
buttonVariant: "outline",
|
|
},
|
|
};
|
|
|
|
export function TaskCard({
|
|
id,
|
|
icon: Icon,
|
|
title,
|
|
description,
|
|
actionLabel,
|
|
detailHref,
|
|
onAction,
|
|
tone = "neutral",
|
|
isLoading = false,
|
|
loadingText,
|
|
className,
|
|
}: TaskCardProps) {
|
|
const styles = toneStyles[tone];
|
|
|
|
const cardContent = (
|
|
<>
|
|
{/* Icon - Larger for prominence */}
|
|
<div
|
|
className={cn(
|
|
"flex-shrink-0 h-12 w-12 rounded-xl flex items-center justify-center",
|
|
styles.iconBg
|
|
)}
|
|
aria-hidden="true"
|
|
>
|
|
<Icon className={cn("h-6 w-6", styles.iconColor)} />
|
|
</div>
|
|
|
|
{/* Content - Larger text */}
|
|
<div className="flex-1 min-w-0">
|
|
<h3 className="text-base font-semibold text-foreground">{title}</h3>
|
|
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">{description}</p>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
const actionButton = (
|
|
<Button
|
|
variant={styles.buttonVariant}
|
|
size="sm"
|
|
onClick={e => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
onAction?.();
|
|
}}
|
|
isLoading={isLoading}
|
|
loadingText={loadingText}
|
|
rightIcon={!isLoading ? <ChevronRightIcon className="h-4 w-4" /> : undefined}
|
|
className="shrink-0"
|
|
>
|
|
{actionLabel}
|
|
</Button>
|
|
);
|
|
|
|
const cardClasses = cn(
|
|
"group flex items-center gap-5 p-5 rounded-2xl border border-border/60",
|
|
"border-l-4",
|
|
styles.border,
|
|
styles.card,
|
|
"transition-all duration-[var(--cp-duration-normal)]",
|
|
"shadow-[var(--cp-shadow-1)] hover:shadow-[var(--cp-shadow-3)]",
|
|
detailHref && "cursor-pointer",
|
|
className
|
|
);
|
|
|
|
if (detailHref) {
|
|
return (
|
|
<Link href={detailHref} data-task-id={id} className={cn(cardClasses, "block")}>
|
|
<div className="flex items-center gap-5 w-full">
|
|
{cardContent}
|
|
{/* Action button */}
|
|
<div className="shrink-0">{actionButton}</div>
|
|
</div>
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div data-task-id={id} className={cardClasses}>
|
|
{cardContent}
|
|
{/* Action button */}
|
|
<div className="shrink-0">{actionButton}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Loading skeleton for TaskCard
|
|
*/
|
|
export function TaskCardSkeleton() {
|
|
return (
|
|
<div className="flex items-center gap-5 p-5 rounded-2xl bg-muted/30 border border-border/60 border-l-4 border-l-muted animate-pulse">
|
|
<div className="flex-shrink-0 h-12 w-12 rounded-xl bg-muted" />
|
|
<div className="flex-1 min-w-0 space-y-2">
|
|
<div className="h-5 bg-muted rounded w-1/3" />
|
|
<div className="h-4 bg-muted rounded w-2/3" />
|
|
</div>
|
|
<div className="h-9 w-24 bg-muted rounded-lg" />
|
|
</div>
|
|
);
|
|
}
|