barsa 1f7f77775b Update dashboard components and styles for improved functionality and consistency
- 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.
2025-12-16 17:10:14 +09:00

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>
);
}