Improve the inventory page
This commit is contained in:
@@ -10,6 +10,7 @@ export interface ProgressBarProps {
|
||||
label?: string;
|
||||
className?: string;
|
||||
animated?: boolean;
|
||||
customColor?: string;
|
||||
}
|
||||
|
||||
const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(({
|
||||
@@ -21,6 +22,7 @@ const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(({
|
||||
label,
|
||||
className,
|
||||
animated = false,
|
||||
customColor,
|
||||
...props
|
||||
}, ref) => {
|
||||
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
||||
@@ -58,14 +60,21 @@ const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(({
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'h-full rounded-full transition-all duration-300 ease-out',
|
||||
variantClasses[variant],
|
||||
'h-full rounded-full transition-all duration-300 ease-out relative overflow-hidden',
|
||||
!customColor && variantClasses[variant],
|
||||
{
|
||||
'animate-pulse': animated && percentage < 100,
|
||||
}
|
||||
)}
|
||||
style={{ width: `${percentage}%` }}
|
||||
/>
|
||||
style={{
|
||||
width: `${percentage}%`,
|
||||
backgroundColor: customColor || undefined
|
||||
}}
|
||||
>
|
||||
{animated && percentage < 100 && (
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent opacity-20 animate-pulse" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { Card } from '../Card';
|
||||
import { Button } from '../Button';
|
||||
import { ProgressBar } from '../ProgressBar';
|
||||
import { statusColors } from '../../../styles/colors';
|
||||
|
||||
export interface StatusIndicatorConfig {
|
||||
@@ -107,66 +108,90 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
||||
const secondaryActions = sortedActions.filter(action => action.priority !== 'primary');
|
||||
|
||||
return (
|
||||
<Card
|
||||
<Card
|
||||
className={`
|
||||
p-5 transition-all duration-200 border-l-4
|
||||
${hasInteraction ? 'hover:shadow-md cursor-pointer' : ''}
|
||||
p-6 transition-all duration-200 border-l-4 hover:shadow-lg
|
||||
${hasInteraction ? 'hover:shadow-xl cursor-pointer hover:scale-[1.02]' : ''}
|
||||
${statusIndicator.isCritical
|
||||
? 'ring-2 ring-red-200 shadow-md border-l-8'
|
||||
: statusIndicator.isHighlight
|
||||
? 'ring-1 ring-yellow-200'
|
||||
: ''
|
||||
}
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
style={{
|
||||
borderLeftColor: statusIndicator.color,
|
||||
backgroundColor: statusIndicator.isCritical
|
||||
? `${statusIndicator.color}08`
|
||||
: statusIndicator.isHighlight
|
||||
? `${statusIndicator.color}05`
|
||||
backgroundColor: statusIndicator.isCritical
|
||||
? `${statusIndicator.color}08`
|
||||
: statusIndicator.isHighlight
|
||||
? `${statusIndicator.color}05`
|
||||
: undefined
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-5">
|
||||
{/* Header with status indicator */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="flex-shrink-0 p-2 rounded-lg"
|
||||
style={{ backgroundColor: `${statusIndicator.color}15` }}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-4 flex-1">
|
||||
<div
|
||||
className={`flex-shrink-0 p-3 rounded-xl shadow-sm ${
|
||||
statusIndicator.isCritical ? 'ring-2 ring-white' : ''
|
||||
}`}
|
||||
style={{ backgroundColor: `${statusIndicator.color}20` }}
|
||||
>
|
||||
{StatusIcon && (
|
||||
<StatusIcon
|
||||
className="w-4 h-4"
|
||||
style={{ color: statusIndicator.color }}
|
||||
<StatusIcon
|
||||
className="w-5 h-5"
|
||||
style={{ color: statusIndicator.color }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-[var(--text-primary)] text-base">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-semibold text-[var(--text-primary)] text-lg leading-tight mb-1">
|
||||
{title}
|
||||
</div>
|
||||
<div
|
||||
className="text-sm font-medium"
|
||||
style={{ color: statusIndicator.color }}
|
||||
>
|
||||
{statusIndicator.text}
|
||||
{statusIndicator.isCritical && (
|
||||
<span className="ml-2 text-xs">⚠️</span>
|
||||
)}
|
||||
{statusIndicator.isHighlight && (
|
||||
<span className="ml-2 text-xs">⭐</span>
|
||||
)}
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<div
|
||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-xs font-semibold transition-all ${
|
||||
statusIndicator.isCritical
|
||||
? 'bg-red-100 text-red-800 ring-2 ring-red-300 shadow-sm animate-pulse'
|
||||
: statusIndicator.isHighlight
|
||||
? 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300 shadow-sm'
|
||||
: 'ring-1 shadow-sm'
|
||||
}`}
|
||||
style={{
|
||||
backgroundColor: statusIndicator.isCritical || statusIndicator.isHighlight
|
||||
? undefined
|
||||
: `${statusIndicator.color}20`,
|
||||
color: statusIndicator.isCritical || statusIndicator.isHighlight
|
||||
? undefined
|
||||
: statusIndicator.color,
|
||||
borderColor: `${statusIndicator.color}40`
|
||||
}}
|
||||
>
|
||||
{statusIndicator.isCritical && (
|
||||
<span className="mr-2 text-sm">🚨</span>
|
||||
)}
|
||||
{statusIndicator.isHighlight && (
|
||||
<span className="mr-1.5">⚠️</span>
|
||||
)}
|
||||
{statusIndicator.text}
|
||||
</div>
|
||||
</div>
|
||||
{subtitle && (
|
||||
<div className="text-xs text-[var(--text-secondary)]">
|
||||
<div className="text-sm text-[var(--text-secondary)] truncate">
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-[var(--text-primary)]">
|
||||
<div className="text-right flex-shrink-0 ml-4">
|
||||
<div className="text-3xl font-bold text-[var(--text-primary)] leading-none">
|
||||
{primaryValue}
|
||||
</div>
|
||||
{primaryValueLabel && (
|
||||
<div className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide">
|
||||
<div className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide mt-1">
|
||||
{primaryValueLabel}
|
||||
</div>
|
||||
)}
|
||||
@@ -187,27 +212,26 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
||||
|
||||
{/* Progress indicator */}
|
||||
{progress && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-[var(--text-secondary)]">
|
||||
{progress.label}
|
||||
</span>
|
||||
<span className="text-sm font-bold text-[var(--text-primary)]">
|
||||
{progress.percentage}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-2">
|
||||
<div
|
||||
className="h-2 rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${Math.min(progress.percentage, 100)}%`,
|
||||
backgroundColor: progress.color || statusIndicator.color
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<ProgressBar
|
||||
value={progress.percentage}
|
||||
max={100}
|
||||
size="md"
|
||||
variant="default"
|
||||
showLabel={true}
|
||||
label={progress.label}
|
||||
animated={progress.percentage < 100}
|
||||
customColor={progress.color || statusIndicator.color}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Spacer for alignment when no progress bar */}
|
||||
{!progress && (
|
||||
<div className="h-12" />
|
||||
)}
|
||||
|
||||
{/* Metadata */}
|
||||
{metadata.length > 0 && (
|
||||
<div className="text-xs text-[var(--text-secondary)] space-y-1">
|
||||
@@ -217,65 +241,69 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Enhanced Mobile-Responsive Actions */}
|
||||
{/* Elegant Action System */}
|
||||
{actions.length > 0 && (
|
||||
<div className="pt-3 border-t border-[var(--border-primary)]">
|
||||
{/* Primary Actions - Full width on mobile, inline on desktop */}
|
||||
<div className="pt-5">
|
||||
{/* Primary Actions Row */}
|
||||
{primaryActions.length > 0 && (
|
||||
<div className={`
|
||||
flex gap-2 mb-2
|
||||
${primaryActions.length > 1 ? 'flex-col sm:flex-row' : ''}
|
||||
`}>
|
||||
{primaryActions.map((action, index) => (
|
||||
<Button
|
||||
key={`primary-${index}`}
|
||||
variant={action.destructive ? 'outline' : (action.variant || 'primary')}
|
||||
size="sm"
|
||||
<div className="flex gap-3 mb-3">
|
||||
<Button
|
||||
variant={primaryActions[0].destructive ? 'outline' : 'primary'}
|
||||
size="md"
|
||||
className={`
|
||||
flex-1 h-11 font-medium justify-center
|
||||
${primaryActions[0].destructive
|
||||
? 'border-red-300 text-red-600 hover:bg-red-50 hover:border-red-400'
|
||||
: 'bg-gradient-to-r from-[var(--color-primary-600)] to-[var(--color-primary-500)] hover:from-[var(--color-primary-700)] hover:to-[var(--color-primary-600)] text-white border-transparent shadow-md hover:shadow-lg'
|
||||
}
|
||||
transition-all duration-200 transform hover:scale-[1.02] active:scale-[0.98]
|
||||
`}
|
||||
onClick={primaryActions[0].onClick}
|
||||
>
|
||||
{primaryActions[0].icon && React.createElement(primaryActions[0].icon, { className: "w-4 h-4 mr-2" })}
|
||||
<span>{primaryActions[0].label}</span>
|
||||
</Button>
|
||||
|
||||
{/* Secondary Action Button */}
|
||||
{primaryActions.length > 1 && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="md"
|
||||
className="h-11 w-11 p-0 border-[var(--border-secondary)] hover:border-[var(--color-primary-400)] hover:bg-[var(--color-primary-50)] transition-all duration-200"
|
||||
onClick={primaryActions[1].onClick}
|
||||
title={primaryActions[1].label}
|
||||
>
|
||||
{primaryActions[1].icon && React.createElement(primaryActions[1].icon, { className: "w-4 h-4" })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Secondary Actions Row - Smaller buttons */}
|
||||
{secondaryActions.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{secondaryActions.map((action, index) => (
|
||||
<Button
|
||||
key={`secondary-${index}`}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={`
|
||||
${primaryActions.length === 1 ? 'w-full sm:w-auto sm:min-w-[120px]' : 'flex-1'}
|
||||
${action.destructive ? 'text-red-600 border-red-300 hover:bg-red-50 hover:border-red-400' : ''}
|
||||
font-medium transition-all duration-200
|
||||
h-8 px-3 text-xs font-medium border-[var(--border-secondary)]
|
||||
${action.destructive
|
||||
? 'text-red-600 border-red-200 hover:bg-red-50 hover:border-red-300'
|
||||
: 'text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:border-[var(--color-primary-300)] hover:bg-[var(--color-primary-50)]'
|
||||
}
|
||||
transition-all duration-200 flex items-center gap-1.5
|
||||
`}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.icon && <action.icon className="w-4 h-4 mr-2 flex-shrink-0" />}
|
||||
<span className="truncate">{action.label}</span>
|
||||
{action.icon && React.createElement(action.icon, { className: "w-3.5 h-3.5" })}
|
||||
<span>{action.label}</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Secondary Actions - Compact horizontal layout */}
|
||||
{secondaryActions.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{secondaryActions.map((action, index) => (
|
||||
<Button
|
||||
key={`secondary-${index}`}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className={`
|
||||
flex-shrink-0 min-w-0
|
||||
${action.destructive ? 'text-red-600 border-red-300 hover:bg-red-50 hover:border-red-400' : ''}
|
||||
${secondaryActions.length > 3 ? 'text-xs px-2' : 'text-sm px-3'}
|
||||
transition-all duration-200
|
||||
`}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.icon && <action.icon className={`
|
||||
${secondaryActions.length > 3 ? 'w-3 h-3' : 'w-4 h-4'}
|
||||
${action.label ? 'mr-1 sm:mr-2' : ''}
|
||||
flex-shrink-0
|
||||
`} />}
|
||||
<span className={`
|
||||
${secondaryActions.length > 3 ? 'hidden sm:inline' : ''}
|
||||
truncate
|
||||
`}>
|
||||
{action.label}
|
||||
</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user