diff --git a/frontend/src/components/domain/recipes/CreateRecipeModal.tsx b/frontend/src/components/domain/recipes/CreateRecipeModal.tsx index 06858ac3..434b5fcb 100644 --- a/frontend/src/components/domain/recipes/CreateRecipeModal.tsx +++ b/frontend/src/components/domain/recipes/CreateRecipeModal.tsx @@ -57,7 +57,7 @@ export const CreateRecipeModal: React.FC = ({ }); const [loading, setLoading] = useState(false); - const [mode, setMode] = useState<'overview' | 'edit'>('edit'); + const [mode, setMode] = useState<'view' | 'edit'>('edit'); // Get tenant and fetch inventory data const currentTenant = useCurrentTenant(); @@ -280,7 +280,8 @@ export const CreateRecipeModal: React.FC = ({ } }; - const getModalSections = () => [ + const getModalSections = () => { + const sections = [ { title: 'Información Básica', icon: ChefHat, @@ -588,12 +589,21 @@ export const CreateRecipeModal: React.FC = ({ } ]; + // Add editable: true to all fields since this is a creation modal + return sections.map(section => ({ + ...section, + fields: section.fields.map(field => ({ + ...field, + editable: field.readonly !== true // Make editable unless explicitly readonly + })) + })); + }; + return ( = ({ size="xl" sections={getModalSections()} onFieldChange={handleFieldChange} - actions={[ - { - label: loading ? 'Creando...' : 'Crear Receta', - icon: ChefHat, - variant: 'primary', - onClick: handleSubmit, - disabled: loading || !formData.name.trim() || !formData.finished_product_id.trim() - } - ]} + showDefaultActions={true} + onSave={handleSubmit} + onCancel={onClose} /> ); }; diff --git a/frontend/src/components/ui/Tabs/Tabs.tsx b/frontend/src/components/ui/Tabs/Tabs.tsx new file mode 100644 index 00000000..5860c71e --- /dev/null +++ b/frontend/src/components/ui/Tabs/Tabs.tsx @@ -0,0 +1,122 @@ +import React from 'react'; +import { Card } from '../Card'; + +export interface TabItem { + id: string; + label: string; + disabled?: boolean; +} + +export interface TabsProps { + items: TabItem[]; + activeTab: string; + onTabChange: (tabId: string) => void; + variant?: 'default' | 'pills' | 'underline'; + size?: 'sm' | 'md' | 'lg'; + fullWidth?: boolean; + className?: string; +} + +const Tabs: React.FC = ({ + items, + activeTab, + onTabChange, + variant = 'pills', + size = 'md', + fullWidth = false, + className = '' +}) => { + // Size classes + const sizeClasses = { + sm: 'px-2 py-1.5 text-xs', + md: 'px-3 sm:px-4 py-3 sm:py-2 text-sm', + lg: 'px-4 sm:px-6 py-4 sm:py-3 text-base' + }; + + // Variant styles + const getVariantClasses = (isActive: boolean, isDisabled: boolean) => { + if (isDisabled) { + return 'text-[var(--text-tertiary)] cursor-not-allowed opacity-50'; + } + + switch (variant) { + case 'pills': + return isActive + ? 'bg-[var(--color-primary)] text-white' + : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-secondary)]'; + + case 'underline': + return isActive + ? 'text-[var(--color-primary)] border-b-2 border-[var(--color-primary)]' + : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)] border-b-2 border-transparent'; + + default: + return isActive + ? 'bg-[var(--color-primary)] text-white' + : 'text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-secondary)]'; + } + }; + + const baseButtonClasses = ` + font-medium transition-colors + ${fullWidth ? 'flex-1' : 'sm:flex-none'} + ${variant === 'pills' ? 'rounded-md' : ''} + ${sizeClasses[size]} + `.trim(); + + if (variant === 'underline') { + return ( +
+ +
+ ); + } + + return ( + +
+ {items.map((item) => { + const isActive = activeTab === item.id; + const isDisabled = item.disabled || false; + + return ( + + ); + })} +
+
+ ); +}; + +export default Tabs; \ No newline at end of file diff --git a/frontend/src/components/ui/Tabs/index.ts b/frontend/src/components/ui/Tabs/index.ts new file mode 100644 index 00000000..9de1ee58 --- /dev/null +++ b/frontend/src/components/ui/Tabs/index.ts @@ -0,0 +1,2 @@ +export { default as Tabs } from './Tabs'; +export type { TabsProps, TabItem } from './Tabs'; \ No newline at end of file diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index dae44605..58c00001 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -9,6 +9,7 @@ export { default as Avatar } from './Avatar'; export { default as Tooltip } from './Tooltip'; export { default as Select } from './Select'; export { default as DatePicker } from './DatePicker'; +export { Tabs } from './Tabs'; export { ThemeToggle } from './ThemeToggle'; export { ProgressBar } from './ProgressBar'; export { StatusIndicator } from './StatusIndicator'; @@ -29,6 +30,7 @@ export type { AvatarProps } from './Avatar'; export type { TooltipProps } from './Tooltip'; export type { SelectProps, SelectOption } from './Select'; export type { DatePickerProps } from './DatePicker'; +export type { TabsProps, TabItem } from './Tabs'; export type { ThemeToggleProps } from './ThemeToggle'; export type { ProgressBarProps } from './ProgressBar'; export type { StatusIndicatorProps } from './StatusIndicator'; diff --git a/frontend/src/pages/app/operations/orders/OrdersPage.tsx b/frontend/src/pages/app/operations/orders/OrdersPage.tsx index f9806bb1..9fe8b231 100644 --- a/frontend/src/pages/app/operations/orders/OrdersPage.tsx +++ b/frontend/src/pages/app/operations/orders/OrdersPage.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { Plus, Clock, Package, Eye, Edit, CheckCircle, AlertCircle, Timer, Users, Loader, Euro } from 'lucide-react'; -import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui'; +import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal, Tabs } from '../../../../components/ui'; import { formatters } from '../../../../components/ui/Stats/StatsPresets'; import { PageHeader } from '../../../../components/layout'; import { @@ -302,31 +302,18 @@ const OrdersPage: React.FC = () => { ]} /> - {/* Tabs - Mobile-friendly */} - -
- - -
-
+ {/* Tabs */} + setActiveTab(tabId as 'orders' | 'customers')} + fullWidth={true} + variant="pills" + size="md" + /> {/* Stats Grid */} { @@ -201,6 +201,12 @@ const RecipesPage: React.FC = () => { cook_time_minutes: typeof editedRecipe.cook_time_minutes === 'string' ? parseInt(editedRecipe.cook_time_minutes.toString()) : editedRecipe.cook_time_minutes, + // Ensure yield_unit is properly typed + yield_unit: editedRecipe.yield_unit ? editedRecipe.yield_unit as MeasurementUnit : undefined, + // Convert difficulty level to number if needed + difficulty_level: typeof editedRecipe.difficulty_level === 'string' + ? parseInt(editedRecipe.difficulty_level.toString()) + : editedRecipe.difficulty_level, }; await updateRecipeMutation.mutateAsync({ @@ -423,31 +429,28 @@ const RecipesPage: React.FC = () => { `${recipe.ingredients?.length || 0} ingredientes principales` ]} actions={[ + // Primary action - View recipe details { - label: 'Ver', + label: 'Ver Detalles', icon: Eye, - variant: 'outline', + variant: 'primary', + priority: 'primary', onClick: () => { setSelectedRecipe(recipe); setModalMode('view'); setShowForm(true); } }, + // Secondary action - Edit recipe { label: 'Editar', icon: Edit, - variant: 'outline', + priority: 'secondary', onClick: () => { setSelectedRecipe(recipe); setModalMode('edit'); setShowForm(true); } - }, - { - label: 'Producir', - icon: ChefHat, - variant: 'primary', - onClick: () => console.log('Produce recipe', recipe.id) } ]} /> @@ -483,45 +486,20 @@ const RecipesPage: React.FC = () => { setEditedRecipe({}); }} mode={modalMode} - onModeChange={setModalMode} + onModeChange={(newMode) => { + setModalMode(newMode); + if (newMode === 'view') { + setEditedRecipe({}); + } + }} title={selectedRecipe.name} subtitle={selectedRecipe.description || ''} statusIndicator={getRecipeStatusConfig(selectedRecipe)} size="xl" sections={getModalSections()} onFieldChange={handleFieldChange} - actions={modalMode === 'edit' ? [ - { - label: 'Guardar', - icon: ChefHat, - variant: 'primary', - onClick: handleSaveRecipe, - disabled: updateRecipeMutation.isPending - }, - { - label: 'Cancelar', - variant: 'outline', - onClick: () => { - setModalMode('view'); - setEditedRecipe({}); - } - } - ] : [ - { - label: 'Producir', - icon: ChefHat, - variant: 'primary', - onClick: () => { - console.log('Producing recipe:', selectedRecipe.id); - setShowForm(false); - setSelectedRecipe(null); - } - } - ]} - onEdit={() => { - setModalMode('edit'); - setEditedRecipe({}); - }} + showDefaultActions={true} + onSave={handleSaveRecipe} /> )}