Improve the inventory page

This commit is contained in:
Urtzi Alfaro
2025-09-17 16:06:30 +02:00
parent 7aa26d51d3
commit dcb3ce441b
39 changed files with 5852 additions and 1762 deletions

View File

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

View File

@@ -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>