Improve the frontend
This commit is contained in:
@@ -63,6 +63,7 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
|
||||
|
||||
const renderItemField = (item: any, itemIndex: number, fieldConfig: any) => {
|
||||
const fieldValue = item[fieldConfig.name] ?? '';
|
||||
const isFieldDisabled = fieldConfig.disabled ?? false;
|
||||
|
||||
switch (fieldConfig.type) {
|
||||
case 'select':
|
||||
@@ -70,10 +71,11 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
|
||||
<select
|
||||
value={fieldValue}
|
||||
onChange={(e) => updateItem(itemIndex, fieldConfig.name, e.target.value)}
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
required={fieldConfig.required}
|
||||
disabled={isFieldDisabled}
|
||||
>
|
||||
<option value="">Seleccionar...</option>
|
||||
<option value="">{fieldConfig.placeholder || 'Seleccionar...'}</option>
|
||||
{fieldConfig.options?.map((option: any) => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
@@ -87,11 +89,12 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
|
||||
type="number"
|
||||
value={fieldValue}
|
||||
onChange={(e) => updateItem(itemIndex, fieldConfig.name, parseFloat(e.target.value) || 0)}
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
min="0"
|
||||
step={fieldConfig.type === 'currency' ? '0.01' : '0.1'}
|
||||
placeholder={fieldConfig.placeholder}
|
||||
required={fieldConfig.required}
|
||||
disabled={isFieldDisabled}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -101,14 +104,17 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
|
||||
type="text"
|
||||
value={fieldValue}
|
||||
onChange={(e) => updateItem(itemIndex, fieldConfig.name, e.target.value)}
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm"
|
||||
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
placeholder={fieldConfig.placeholder}
|
||||
required={fieldConfig.required}
|
||||
disabled={isFieldDisabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const isDisabled = listConfig.disabled ?? false;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -116,7 +122,12 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
|
||||
<button
|
||||
type="button"
|
||||
onClick={addItem}
|
||||
className="flex items-center gap-2 px-3 py-1.5 text-sm bg-[var(--color-primary)] text-white rounded-md hover:bg-[var(--color-primary)]/90 transition-colors"
|
||||
disabled={isDisabled}
|
||||
className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-md transition-colors ${
|
||||
isDisabled
|
||||
? 'bg-gray-300 text-gray-500 cursor-not-allowed opacity-50'
|
||||
: 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary)]/90'
|
||||
}`}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
{listConfig.addButtonLabel || t('common:modals.actions.add', 'Agregar')}
|
||||
@@ -129,7 +140,7 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
|
||||
<Plus className="w-full h-full" />
|
||||
</div>
|
||||
<p>{listConfig.emptyStateText || 'No hay elementos agregados'}</p>
|
||||
<p className="text-sm">Haz clic en "{listConfig.addButtonLabel || 'Agregar'}" para comenzar</p>
|
||||
{!isDisabled && <p className="text-sm">Haz clic en "{listConfig.addButtonLabel || 'Agregar'}" para comenzar</p>}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||||
@@ -204,12 +215,14 @@ export interface AddModalField {
|
||||
options?: Array<{label: string; value: string | number}>;
|
||||
defaultValue?: any;
|
||||
validation?: (value: any) => string | null;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
addButtonLabel?: string;
|
||||
removeButtonLabel?: string;
|
||||
emptyStateText?: string;
|
||||
showSubtotals?: boolean; // For calculating item totals
|
||||
subtotalFields?: { quantity: string; price: string }; // Field names for calculation
|
||||
disabled?: boolean; // Disable adding new items
|
||||
};
|
||||
}
|
||||
|
||||
@@ -686,8 +699,7 @@ export const AddModal: React.FC<AddModalProps> = ({
|
||||
disabled={loading}
|
||||
className="min-w-[80px]"
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
{t('common:modals.actions.cancel', 'Cancelar')}
|
||||
{t('common:modals.actions.cancel', 'Cancelar')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -698,10 +710,7 @@ export const AddModal: React.FC<AddModalProps> = ({
|
||||
{loading ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current"></div>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-4 h-4 mr-2" />
|
||||
{t('common:modals.actions.save', 'Guardar')}
|
||||
</>
|
||||
t('common:modals.actions.save', 'Guardar')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -54,28 +54,52 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(({
|
||||
'whitespace-nowrap',
|
||||
];
|
||||
|
||||
// Variant styling using CSS custom properties
|
||||
const variantStyles: Record<string, React.CSSProperties> = {
|
||||
default: {},
|
||||
primary: {
|
||||
backgroundColor: 'var(--color-primary)',
|
||||
color: 'white',
|
||||
borderColor: 'var(--color-primary)',
|
||||
},
|
||||
secondary: {
|
||||
backgroundColor: 'var(--color-secondary)',
|
||||
color: 'white',
|
||||
borderColor: 'var(--color-secondary)',
|
||||
},
|
||||
success: {
|
||||
backgroundColor: 'var(--color-success)',
|
||||
color: 'white',
|
||||
borderColor: 'var(--color-success)',
|
||||
},
|
||||
warning: {
|
||||
backgroundColor: 'var(--color-warning)',
|
||||
color: 'white',
|
||||
borderColor: 'var(--color-warning)',
|
||||
},
|
||||
error: {
|
||||
backgroundColor: 'var(--color-error)',
|
||||
color: 'white',
|
||||
borderColor: 'var(--color-error)',
|
||||
},
|
||||
info: {
|
||||
backgroundColor: 'var(--color-info)',
|
||||
color: 'white',
|
||||
borderColor: 'var(--color-info)',
|
||||
},
|
||||
outline: {},
|
||||
};
|
||||
|
||||
const variantClasses = {
|
||||
default: [
|
||||
'bg-bg-tertiary text-text-primary border border-border-primary',
|
||||
],
|
||||
primary: [
|
||||
'bg-color-primary text-text-inverse',
|
||||
],
|
||||
secondary: [
|
||||
'bg-color-secondary text-text-inverse',
|
||||
],
|
||||
success: [
|
||||
'bg-color-success text-text-inverse',
|
||||
],
|
||||
warning: [
|
||||
'bg-color-warning text-text-inverse',
|
||||
],
|
||||
error: [
|
||||
'bg-color-error text-text-inverse',
|
||||
],
|
||||
info: [
|
||||
'bg-color-info text-text-inverse',
|
||||
'bg-[var(--bg-tertiary)] text-[var(--text-primary)] border border-[var(--border-primary)]',
|
||||
],
|
||||
primary: [],
|
||||
secondary: [],
|
||||
success: [],
|
||||
warning: [],
|
||||
error: [],
|
||||
info: [],
|
||||
outline: [
|
||||
'bg-transparent border border-current',
|
||||
],
|
||||
@@ -83,13 +107,13 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(({
|
||||
|
||||
const sizeClasses = {
|
||||
xs: isStandalone ? 'px-1.5 py-0.5 text-xs min-h-4' : 'w-4 h-4 text-xs',
|
||||
sm: isStandalone ? 'px-2 py-0.5 text-xs min-h-5' : 'w-5 h-5 text-xs',
|
||||
md: isStandalone ? 'px-2.5 py-1 text-sm min-h-6' : 'w-6 h-6 text-sm',
|
||||
lg: isStandalone ? 'px-3 py-1.5 text-sm min-h-7' : 'w-7 h-7 text-sm',
|
||||
sm: isStandalone ? 'px-3 py-1.5 text-sm min-h-6 font-medium' : 'w-5 h-5 text-xs',
|
||||
md: isStandalone ? 'px-3 py-1.5 text-sm min-h-7 font-semibold' : 'w-6 h-6 text-sm',
|
||||
lg: isStandalone ? 'px-4 py-2 text-base min-h-8 font-semibold' : 'w-7 h-7 text-sm',
|
||||
};
|
||||
|
||||
const shapeClasses = {
|
||||
rounded: 'rounded-md',
|
||||
rounded: 'rounded-lg',
|
||||
pill: 'rounded-full',
|
||||
square: 'rounded-none',
|
||||
};
|
||||
@@ -171,18 +195,22 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(({
|
||||
variantClasses[variant],
|
||||
sizeClasses[size],
|
||||
shapeClasses[shape],
|
||||
'border', // Always include border
|
||||
{
|
||||
'gap-1': icon || closable,
|
||||
'pr-1': closable,
|
||||
'gap-2': icon || closable,
|
||||
'pr-2': closable,
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
const customStyle = color ? {
|
||||
backgroundColor: color,
|
||||
borderColor: color,
|
||||
color: getContrastColor(color),
|
||||
} : undefined;
|
||||
// Merge custom style with variant style
|
||||
const customStyle = color
|
||||
? {
|
||||
backgroundColor: color,
|
||||
borderColor: color,
|
||||
color: getContrastColor(color),
|
||||
}
|
||||
: variantStyles[variant] || {};
|
||||
|
||||
return (
|
||||
<span
|
||||
@@ -192,9 +220,9 @@ const Badge = forwardRef<HTMLSpanElement, BadgeProps>(({
|
||||
{...props}
|
||||
>
|
||||
{icon && (
|
||||
<span className="flex-shrink-0">{icon}</span>
|
||||
<span className="flex-shrink-0 flex items-center">{icon}</span>
|
||||
)}
|
||||
<span>{text || displayCount || children}</span>
|
||||
<span className="whitespace-nowrap">{text || displayCount || children}</span>
|
||||
{closable && onClose && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface StatusCardProps {
|
||||
onClick: () => void;
|
||||
priority?: 'primary' | 'secondary' | 'tertiary';
|
||||
destructive?: boolean;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
@@ -292,14 +293,19 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
primaryActions[0].onClick();
|
||||
if (!primaryActions[0].disabled) {
|
||||
primaryActions[0].onClick();
|
||||
}
|
||||
}}
|
||||
disabled={primaryActions[0].disabled}
|
||||
className={`
|
||||
flex items-center gap-1 sm:gap-2 px-2 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm font-medium rounded-lg
|
||||
transition-all duration-200 hover:scale-105 active:scale-95 flex-shrink-0 max-w-[120px] sm:max-w-[150px]
|
||||
${primaryActions[0].destructive
|
||||
? 'text-red-600 hover:bg-red-50 hover:text-red-700'
|
||||
: 'text-[var(--color-primary-600)] hover:bg-[var(--color-primary-50)] hover:text-[var(--color-primary-700)]'
|
||||
${primaryActions[0].disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: primaryActions[0].destructive
|
||||
? 'text-red-600 hover:bg-red-50 hover:text-red-700'
|
||||
: 'text-[var(--color-primary-600)] hover:bg-[var(--color-primary-50)] hover:text-[var(--color-primary-700)]'
|
||||
}
|
||||
`}
|
||||
title={primaryActions[0].label}
|
||||
@@ -318,14 +324,19 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
||||
key={`action-${index}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
action.onClick();
|
||||
if (!action.disabled) {
|
||||
action.onClick();
|
||||
}
|
||||
}}
|
||||
disabled={action.disabled}
|
||||
title={action.label}
|
||||
className={`
|
||||
p-1.5 sm:p-2 rounded-lg transition-all duration-200 hover:scale-110 active:scale-95
|
||||
${action.destructive
|
||||
? 'text-red-500 hover:bg-red-50 hover:text-red-600'
|
||||
: 'text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--surface-secondary)]'
|
||||
${action.disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: action.destructive
|
||||
? 'text-red-500 hover:bg-red-50 hover:text-red-600'
|
||||
: 'text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--surface-secondary)]'
|
||||
}
|
||||
`}
|
||||
>
|
||||
@@ -339,14 +350,19 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
||||
key={`primary-icon-${index}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
action.onClick();
|
||||
if (!action.disabled) {
|
||||
action.onClick();
|
||||
}
|
||||
}}
|
||||
disabled={action.disabled}
|
||||
title={action.label}
|
||||
className={`
|
||||
p-1.5 sm:p-2 rounded-lg transition-all duration-200 hover:scale-110 active:scale-95
|
||||
${action.destructive
|
||||
? 'text-red-500 hover:bg-red-50 hover:text-red-600'
|
||||
: 'text-[var(--color-primary-500)] hover:text-[var(--color-primary-600)] hover:bg-[var(--color-primary-50)]'
|
||||
${action.disabled
|
||||
? 'opacity-50 cursor-not-allowed'
|
||||
: action.destructive
|
||||
? 'text-red-500 hover:bg-red-50 hover:text-red-600'
|
||||
: 'text-[var(--color-primary-500)] hover:text-[var(--color-primary-600)] hover:bg-[var(--color-primary-50)]'
|
||||
}
|
||||
`}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user