Fix UI issues

This commit is contained in:
Urtzi Alfaro
2025-09-20 08:59:12 +02:00
parent 66ef2121a1
commit abe7cf2444
19 changed files with 1327 additions and 2277 deletions

View File

@@ -1,6 +1,6 @@
import React, { useState, useCallback, useEffect } from 'react';
import { Card, Button, Badge, Input, Modal, Table, Select, DatePicker } from '../../ui';
import { productionService, type ProductionBatchResponse, ProductionBatchStatus, ProductionPriority } from '../../../api';
import { productionService, type ProductionBatchResponse, ProductionBatchStatus, ProductionPriorityEnum } from '../../../api';
import type { ProductionBatch, QualityCheck } from '../../../types/production.types';
interface BatchTrackerProps {
@@ -124,10 +124,10 @@ const STATUS_COLORS = {
};
const PRIORITY_COLORS = {
[ProductionPriority.LOW]: 'bg-[var(--bg-tertiary)] text-[var(--text-primary)]',
[ProductionPriority.NORMAL]: 'bg-[var(--color-info)]/10 text-[var(--color-info)]',
[ProductionPriority.HIGH]: 'bg-[var(--color-primary)]/10 text-[var(--color-primary)]',
[ProductionPriority.URGENT]: 'bg-[var(--color-error)]/10 text-[var(--color-error)]',
[ProductionPriorityEnum.LOW]: 'bg-[var(--bg-tertiary)] text-[var(--text-primary)]',
[ProductionPriorityEnum.NORMAL]: 'bg-[var(--color-info)]/10 text-[var(--color-info)]',
[ProductionPriorityEnum.HIGH]: 'bg-[var(--color-primary)]/10 text-[var(--color-primary)]',
[ProductionPriorityEnum.URGENT]: 'bg-[var(--color-error)]/10 text-[var(--color-error)]',
};
export const BatchTracker: React.FC<BatchTrackerProps> = ({
@@ -390,10 +390,10 @@ export const BatchTracker: React.FC<BatchTrackerProps> = ({
<h4 className="font-semibold text-[var(--text-primary)]">{batch.recipe?.name || 'Producto'}</h4>
<p className="text-sm text-[var(--text-secondary)]">Lote #{batch.batch_number}</p>
<Badge className={PRIORITY_COLORS[batch.priority]} size="sm">
{batch.priority === ProductionPriority.LOW && 'Baja'}
{batch.priority === ProductionPriority.NORMAL && 'Normal'}
{batch.priority === ProductionPriority.HIGH && 'Alta'}
{batch.priority === ProductionPriority.URGENT && 'Urgente'}
{batch.priority === ProductionPriorityEnum.LOW && 'Baja'}
{batch.priority === ProductionPriorityEnum.NORMAL && 'Normal'}
{batch.priority === ProductionPriorityEnum.HIGH && 'Alta'}
{batch.priority === ProductionPriorityEnum.URGENT && 'Urgente'}
</Badge>
</div>

View File

@@ -18,7 +18,7 @@ export interface ModalProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onClos
}
export interface ModalHeaderProps extends HTMLAttributes<HTMLDivElement> {
title?: string;
title?: string | React.ReactNode;
subtitle?: string;
showCloseButton?: boolean;
onClose?: () => void;
@@ -238,12 +238,18 @@ const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(({
<div className="flex items-start justify-between">
<div className="flex-1">
{title && (
<h2
id="modal-title"
className="text-lg font-semibold text-[var(--text-primary)]"
>
{title}
</h2>
typeof title === 'string' ? (
<h2
id="modal-title"
className="text-lg font-semibold text-[var(--text-primary)]"
>
{title}
</h2>
) : (
<div id="modal-title">
{title}
</div>
)
)}
{subtitle && (
<p className="mt-1 text-sm text-[var(--text-secondary)]">

View File

@@ -283,12 +283,12 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
const triggerClasses = [
'flex items-center justify-between w-full px-3 py-2',
'bg-input-bg border border-input-border rounded-lg',
'text-left transition-colors duration-200',
'focus:border-input-border-focus focus:ring-1 focus:ring-input-border-focus',
'bg-[var(--bg-primary,#ffffff)] border border-[var(--border-primary,#e5e7eb)] rounded-lg',
'text-[var(--text-primary,#111827)] text-left transition-colors duration-200',
'focus:border-[var(--color-primary,#3b82f6)] focus:ring-1 focus:ring-[var(--color-primary,#3b82f6)]',
{
'border-input-border-error focus:border-input-border-error focus:ring-input-border-error': hasError,
'bg-bg-secondary border-transparent focus:bg-input-bg focus:border-input-border-focus': variant === 'filled',
'border-[var(--color-error,#ef4444)] focus:border-[var(--color-error,#ef4444)] focus:ring-[var(--color-error,#ef4444)]': hasError,
'bg-[var(--bg-secondary,#f9fafb)] border-transparent focus:bg-[var(--bg-primary,#ffffff)] focus:border-[var(--color-primary,#3b82f6)]': variant === 'filled',
'bg-transparent border-none focus:ring-0': variant === 'unstyled',
}
];
@@ -300,7 +300,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
};
const dropdownClasses = [
'absolute z-50 w-full mt-1 bg-dropdown-bg border border-dropdown-border rounded-lg shadow-lg',
'absolute z-50 w-full mt-1 bg-[var(--bg-primary,#ffffff)] border border-[var(--border-primary,#e5e7eb)] rounded-lg shadow-lg',
'transform transition-all duration-200 ease-out',
{
'opacity-0 scale-95 pointer-events-none': !isOpen,
@@ -317,7 +317,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
if (multiple && Array.isArray(currentValue)) {
if (currentValue.length === 0) {
return <span className="text-input-placeholder">{placeholder}</span>;
return <span className="text-[var(--text-tertiary,#6b7280)]">{placeholder}</span>;
}
if (currentValue.length === 1) {
@@ -338,7 +338,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
);
}
return <span className="text-input-placeholder">{placeholder}</span>;
return <span className="text-[var(--text-tertiary,#6b7280)]">{placeholder}</span>;
};
const renderMultipleValues = () => {
@@ -354,14 +354,14 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
{selectedOptions.map(option => (
<span
key={option.value}
className="inline-flex items-center gap-1 px-2 py-1 bg-bg-tertiary text-text-primary rounded text-sm"
className="inline-flex items-center gap-1 px-2 py-1 bg-[var(--bg-tertiary,#f3f4f6)] text-[var(--text-primary,#111827)] rounded text-sm"
>
{option.icon && <span className="text-xs">{option.icon}</span>}
<span>{option.label}</span>
<button
type="button"
onClick={(e) => handleRemoveOption(option.value, e)}
className="text-text-tertiary hover:text-text-primary transition-colors duration-150"
className="text-[var(--text-tertiary,#6b7280)] hover:text-[var(--text-primary,#111827)] transition-colors duration-150"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -379,7 +379,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
const renderOptions = () => {
if (loading) {
return (
<div className="px-3 py-2 text-text-secondary">
<div className="px-3 py-2 text-[var(--text-secondary,#4b5563)]">
{loadingMessage}
</div>
);
@@ -387,13 +387,13 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
if (filteredOptions.length === 0) {
return (
<div className="px-3 py-2 text-text-secondary">
<div className="px-3 py-2 text-[var(--text-secondary,#4b5563)]">
{noOptionsMessage}
{createable && searchTerm.trim() && (
<button
type="button"
onClick={handleCreate}
className="block w-full text-left px-3 py-2 text-color-primary hover:bg-dropdown-item-hover transition-colors duration-150"
className="block w-full text-left px-3 py-2 text-[var(--color-primary,#3b82f6)] hover:bg-[var(--bg-secondary,#f9fafb)] transition-colors duration-150"
>
{createLabel} "{searchTerm.trim()}"
</button>
@@ -416,8 +416,8 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
className={clsx(
'flex items-center justify-between px-3 py-2 cursor-pointer transition-colors duration-150',
{
'bg-dropdown-item-hover': isHighlighted,
'bg-color-primary/10 text-color-primary': isSelected && !multiple,
'bg-[var(--bg-secondary,#f9fafb)]': isHighlighted,
'bg-[var(--color-primary,#3b82f6)]/10 text-[var(--color-primary,#3b82f6)]': isSelected && !multiple,
'opacity-50 cursor-not-allowed': option.disabled,
}
)}
@@ -430,19 +430,22 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
type="checkbox"
checked={isSelected}
readOnly
className="rounded border-input-border text-color-primary focus:ring-color-primary"
className="rounded border-[var(--border-primary,#e5e7eb)] text-[var(--color-primary,#3b82f6)] focus:ring-[var(--color-primary,#3b82f6)]"
/>
)}
{option.icon && <span>{option.icon}</span>}
<div className="flex-1">
<div className="font-medium">{option.label}</div>
{option.description && (
<div className="text-xs text-text-secondary">{option.description}</div>
{option.description &&
!option.description.startsWith('descriptions.') &&
!option.description.includes('.') &&
option.description !== option.value && (
<div className="text-xs text-[var(--text-secondary,#4b5563)]">{option.description}</div>
)}
</div>
</div>
{isSelected && !multiple && (
<svg className="w-4 h-4 text-color-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg className="w-4 h-4 text-[var(--color-primary,#3b82f6)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
)}
@@ -460,7 +463,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
// Add grouped options
Object.entries(groupedOptions.groups).forEach(([groupName, groupOptions]) => {
allOptions.push(
<div key={groupName} className="px-3 py-1 text-xs font-semibold text-text-tertiary uppercase tracking-wide">
<div key={groupName} className="px-3 py-1 text-xs font-semibold text-[var(--text-tertiary,#6b7280)] uppercase tracking-wide">
{groupName}
</div>
);
@@ -481,7 +484,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
key="__create__"
type="button"
onClick={handleCreate}
className="block w-full text-left px-3 py-2 text-color-primary hover:bg-dropdown-item-hover transition-colors duration-150 border-t border-border-primary"
className="block w-full text-left px-3 py-2 text-[var(--color-primary,#3b82f6)] hover:bg-[var(--bg-secondary,#f9fafb)] transition-colors duration-150 border-t border-[var(--border-primary,#e5e7eb)]"
>
{createLabel} "{searchTerm.trim()}"
</button>
@@ -496,11 +499,11 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
{label && (
<label
htmlFor={selectId}
className="block text-sm font-medium text-text-primary mb-2"
className="block text-sm font-medium text-[var(--text-primary,#111827)] mb-2"
>
{label}
{isRequired && (
<span className="text-color-error ml-1">*</span>
<span className="text-[var(--color-error,#ef4444)] ml-1">*</span>
)}
</label>
)}
@@ -531,7 +534,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
<button
type="button"
onClick={handleClear}
className="text-text-tertiary hover:text-text-primary transition-colors duration-150 p-1"
className="text-[var(--text-tertiary,#6b7280)] hover:text-[var(--text-primary,#111827)] transition-colors duration-150 p-1"
tabIndex={-1}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -541,7 +544,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
)}
<svg
className={clsx('w-4 h-4 text-text-tertiary transition-transform duration-200', {
className={clsx('w-4 h-4 text-[var(--text-tertiary,#6b7280)] transition-transform duration-200', {
'rotate-180': isOpen,
})}
fill="none"
@@ -555,14 +558,14 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
<div className={clsx(dropdownClasses)} style={{ maxHeight: isOpen ? maxHeight : 0 }}>
{searchable && (
<div className="p-2 border-b border-border-primary">
<div className="p-2 border-b border-[var(--border-primary,#e5e7eb)]">
<input
ref={searchInputRef}
type="text"
placeholder="Buscar..."
value={searchTerm}
onChange={handleSearchChange}
className="w-full px-3 py-2 border border-input-border rounded bg-input-bg focus:outline-none focus:ring-2 focus:ring-color-primary/20"
className="w-full px-3 py-2 border border-[var(--border-primary,#e5e7eb)] rounded bg-[var(--bg-primary,#ffffff)] text-[var(--text-primary,#111827)] focus:outline-none focus:ring-2 focus:ring-[var(--color-primary,#3b82f6)]/20"
onClick={(e) => e.stopPropagation()}
/>
</div>
@@ -579,13 +582,13 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
</div>
{error && (
<p className="mt-2 text-sm text-color-error">
<p className="mt-2 text-sm text-[var(--color-error,#ef4444)]">
{error}
</p>
)}
{helperText && !error && (
<p className="mt-2 text-sm text-text-secondary">
<p className="mt-2 text-sm text-[var(--text-secondary,#4b5563)]">
{helperText}
</p>
)}

View File

@@ -3,6 +3,7 @@ import { LucideIcon, Edit, Eye, X } from 'lucide-react';
import Modal, { ModalHeader, ModalBody, ModalFooter } from '../Modal/Modal';
import { Button } from '../Button';
import { Input } from '../Input';
import { Select } from '../Select';
import { StatusIndicatorConfig, getStatusColor } from '../StatusCard';
import { formatters } from '../Stats/StatsPresets';
@@ -181,7 +182,11 @@ const renderEditableField = (
return (
<textarea
value={Array.isArray(field.value) ? field.value.join('\n') : String(field.value)}
onChange={(e) => onChange?.(e.target.value.split('\n'))}
onChange={(e) => {
const stringArray = e.target.value.split('\n');
// For list type, we'll pass the joined string instead of array to maintain compatibility
onChange?.(stringArray.join('\n'));
}}
placeholder={field.placeholder || 'Una opción por línea'}
required={field.required}
rows={4}
@@ -190,23 +195,15 @@ const renderEditableField = (
);
case 'select':
return (
<select
<Select
value={String(field.value)}
onChange={(e) => onChange?.(e.target.value)}
required={field.required}
className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-md focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent bg-[var(--bg-primary)]"
>
{field.placeholder && (
<option value="" disabled>
{field.placeholder}
</option>
)}
{field.options?.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
</option>
))}
</select>
onChange={(value) => onChange?.(typeof value === 'string' ? value : String(value))}
options={field.options || []}
placeholder={field.placeholder}
isRequired={field.required}
variant="outline"
size="md"
/>
);
default:
return (