Merge pull request #17 from ualsweb/claude/bakery-settings-ux-redesign-017J8nkGyr5NagisnW1TRPSs

Claude/bakery settings ux redesign 017 j8nk gyr5 nagisn w1 trp ss
This commit is contained in:
ualsweb
2025-11-14 07:49:47 +01:00
committed by GitHub
16 changed files with 1599 additions and 1261 deletions

View File

@@ -0,0 +1,180 @@
import React from 'react';
import { HelpCircle } from 'lucide-react';
import { Tooltip } from '../Tooltip';
import { Toggle } from '../Toggle';
import { Input } from '../Input';
import { Select } from '../Select';
import { Badge } from '../Badge';
export interface SettingRowProps {
label: string;
description?: string;
helpText?: string;
icon?: React.ReactNode;
badge?: {
text: string;
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
};
// For toggle type
type?: 'toggle' | 'input' | 'select' | 'custom';
checked?: boolean;
onToggle?: (checked: boolean) => void;
// For input type
inputType?: 'text' | 'number' | 'email' | 'tel' | 'password';
value?: string | number;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
min?: number;
max?: number;
step?: number;
error?: string;
// For select type
options?: Array<{ value: string; label: string }>;
selectValue?: string;
onSelectChange?: (value: string) => void;
// For custom content
children?: React.ReactNode;
// Common props
disabled?: boolean;
className?: string;
required?: boolean;
}
const SettingRow: React.FC<SettingRowProps> = ({
label,
description,
helpText,
icon,
badge,
type = 'toggle',
checked,
onToggle,
inputType = 'text',
value,
onChange,
placeholder,
min,
max,
step,
error,
options = [],
selectValue,
onSelectChange,
children,
disabled = false,
className = '',
required = false,
}) => {
const renderControl = () => {
switch (type) {
case 'toggle':
return (
<Toggle
checked={checked || false}
onChange={onToggle || (() => {})}
disabled={disabled}
size="md"
/>
);
case 'input':
return (
<div className="w-full max-w-xs">
<Input
type={inputType}
value={value}
onChange={onChange}
placeholder={placeholder}
min={min}
max={max}
step={step}
error={error}
disabled={disabled}
required={required}
/>
</div>
);
case 'select':
return (
<div className="w-full max-w-xs">
<Select
options={options}
value={selectValue}
onChange={onSelectChange}
disabled={disabled}
required={required}
/>
</div>
);
case 'custom':
return children;
default:
return null;
}
};
return (
<div className={`
flex flex-col sm:flex-row sm:items-center sm:justify-between
gap-3 sm:gap-6 py-4 px-4 sm:px-6
border-b border-[var(--border-primary)] last:border-b-0
hover:bg-[var(--bg-secondary)] transition-colors
${className}
`}>
{/* Left side - Label and Description */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
{icon && (
<div className="flex-shrink-0 text-[var(--text-secondary)]">
{icon}
</div>
)}
<label className="text-sm font-medium text-[var(--text-primary)] flex items-center gap-2">
{label}
{required && <span className="text-red-500">*</span>}
</label>
{badge && (
<Badge variant={badge.variant || 'default'} size="sm">
{badge.text}
</Badge>
)}
{helpText && (
<Tooltip content={helpText}>
<HelpCircle className="w-4 h-4 text-[var(--text-tertiary)] cursor-help" />
</Tooltip>
)}
</div>
{description && (
<p className="text-xs text-[var(--text-secondary)] mt-1">
{description}
</p>
)}
{error && (
<p className="text-xs text-red-500 mt-1">
{error}
</p>
)}
</div>
{/* Right side - Control */}
<div className="flex-shrink-0">
{renderControl()}
</div>
</div>
);
};
export default SettingRow;

View File

@@ -0,0 +1,2 @@
export { default as SettingRow } from './SettingRow';
export type { SettingRowProps } from './SettingRow';

View File

@@ -0,0 +1,108 @@
import React, { useState } from 'react';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { Card } from '../Card';
import { Badge } from '../Badge';
export interface SettingSectionProps {
title: string;
description?: string;
icon?: React.ReactNode;
badge?: {
text: string;
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
};
collapsible?: boolean;
defaultOpen?: boolean;
children: React.ReactNode;
className?: string;
headerAction?: React.ReactNode;
}
const SettingSection: React.FC<SettingSectionProps> = ({
title,
description,
icon,
badge,
collapsible = false,
defaultOpen = true,
children,
className = '',
headerAction,
}) => {
const [isOpen, setIsOpen] = useState(defaultOpen);
const handleToggle = () => {
if (collapsible) {
setIsOpen(!isOpen);
}
};
return (
<Card className={`overflow-hidden ${className}`}>
{/* Header */}
<div
className={`
px-4 sm:px-6 py-4
${collapsible ? 'cursor-pointer hover:bg-[var(--bg-secondary)]' : ''}
transition-colors
`}
onClick={handleToggle}
>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 flex-1 min-w-0">
{icon && (
<div className="flex-shrink-0 mt-0.5 text-[var(--color-primary)]">
{icon}
</div>
)}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-base sm:text-lg font-semibold text-[var(--text-primary)]">
{title}
</h3>
{badge && (
<Badge variant={badge.variant || 'default'} size="sm">
{badge.text}
</Badge>
)}
{collapsible && (
<div className="ml-auto flex-shrink-0">
{isOpen ? (
<ChevronUp className="w-5 h-5 text-[var(--text-secondary)]" />
) : (
<ChevronDown className="w-5 h-5 text-[var(--text-secondary)]" />
)}
</div>
)}
</div>
{description && (
<p className="text-sm text-[var(--text-secondary)] mt-1">
{description}
</p>
)}
</div>
</div>
{headerAction && !collapsible && (
<div className="flex-shrink-0">
{headerAction}
</div>
)}
</div>
</div>
{/* Content */}
{(!collapsible || isOpen) && (
<div className="border-t border-[var(--border-primary)]">
{children}
</div>
)}
</Card>
);
};
export default SettingSection;

View File

@@ -0,0 +1,2 @@
export { default as SettingSection } from './SettingSection';
export type { SettingSectionProps } from './SettingSection';

View File

@@ -0,0 +1,63 @@
import React, { useState, useEffect } from 'react';
import { Search, X } from 'lucide-react';
export interface SettingsSearchProps {
placeholder?: string;
onSearch: (query: string) => void;
className?: string;
}
const SettingsSearch: React.FC<SettingsSearchProps> = ({
placeholder = 'Search settings...',
onSearch,
className = '',
}) => {
const [query, setQuery] = useState('');
useEffect(() => {
const debounceTimer = setTimeout(() => {
onSearch(query);
}, 300);
return () => clearTimeout(debounceTimer);
}, [query, onSearch]);
const handleClear = () => {
setQuery('');
onSearch('');
};
return (
<div className={`relative ${className}`}>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-[var(--text-tertiary)]" />
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={placeholder}
className="w-full pl-10 pr-10 py-2.5 border border-[var(--border-primary)] rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] placeholder-[var(--text-tertiary)] focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent transition-all"
/>
{query && (
<button
onClick={handleClear}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] transition-colors"
aria-label="Clear search"
>
<X className="w-5 h-5" />
</button>
)}
</div>
{query && (
<div className="absolute top-full left-0 right-0 mt-2 p-2 bg-[var(--bg-secondary)] border border-[var(--border-primary)] rounded-lg shadow-lg z-10">
<p className="text-xs text-[var(--text-secondary)]">
Searching for: <span className="font-semibold text-[var(--text-primary)]">"{query}"</span>
</p>
</div>
)}
</div>
);
};
export default SettingsSearch;

View File

@@ -0,0 +1,2 @@
export { default as SettingsSearch } from './SettingsSearch';
export type { SettingsSearchProps } from './SettingsSearch';

View File

@@ -40,6 +40,9 @@ export { TableOfContents } from './TableOfContents';
export { SavingsCalculator } from './SavingsCalculator'; export { SavingsCalculator } from './SavingsCalculator';
export { StepTimeline } from './StepTimeline'; export { StepTimeline } from './StepTimeline';
export { FAQAccordion } from './FAQAccordion'; export { FAQAccordion } from './FAQAccordion';
export { SettingRow } from './SettingRow';
export { SettingSection } from './SettingSection';
export { SettingsSearch } from './SettingsSearch';
// Export types // Export types
export type { ButtonProps } from './Button'; export type { ButtonProps } from './Button';
@@ -78,3 +81,6 @@ export type { TableOfContentsProps, TOCSection } from './TableOfContents';
export type { SavingsCalculatorProps } from './SavingsCalculator'; export type { SavingsCalculatorProps } from './SavingsCalculator';
export type { StepTimelineProps, TimelineStep } from './StepTimeline'; export type { StepTimelineProps, TimelineStep } from './StepTimeline';
export type { FAQAccordionProps, FAQItem } from './FAQAccordion'; export type { FAQAccordionProps, FAQItem } from './FAQAccordion';
export type { SettingRowProps } from './SettingRow';
export type { SettingSectionProps } from './SettingSection';
export type { SettingsSearchProps } from './SettingsSearch';

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Package, AlertCircle, Thermometer, Clock } from 'lucide-react'; import { Package, AlertCircle, Thermometer, Clock } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Card, Input, SettingSection, SettingRow } from '../../../../../components/ui';
import type { InventorySettings } from '../../../../../api/types/settings'; import type { InventorySettings } from '../../../../../api/types/settings';
interface InventorySettingsCardProps { interface InventorySettingsCardProps {
@@ -23,21 +23,24 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
onChange({ ...settings, [field]: value }); onChange({ ...settings, [field]: value });
}; };
return ( const handleToggleChange = (field: keyof InventorySettings) => (checked: boolean) => {
<Card className="p-6"> onChange({ ...settings, [field]: checked });
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> };
<Package className="w-5 h-5 mr-2 text-[var(--color-primary)]" />
Gestión de Inventario
</h3>
<div className="space-y-6"> return (
{/* Stock Management */} <SettingSection
<div> title="Gestión de Inventario"
description="Configure stock management, expiration tracking, and temperature monitoring"
icon={<Package className="w-5 h-5" />}
>
<div className="divide-y divide-[var(--border-primary)]">
{/* Stock Management Section */}
<div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Package className="w-4 h-4 mr-2" /> <Package className="w-4 h-4 mr-2" />
Control de Stock Control de Stock
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<Input <Input
type="number" type="number"
label="Umbral de Stock Bajo" label="Umbral de Stock Bajo"
@@ -76,13 +79,13 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
</div> </div>
</div> </div>
{/* Expiration Management */} {/* Expiration Management Section */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Clock className="w-4 h-4 mr-2" /> <Clock className="w-4 h-4 mr-2" />
Gestión de Caducidad Gestión de Caducidad
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<Input <Input
type="number" type="number"
label="Días para 'Próximo a Caducar'" label="Días para 'Próximo a Caducar'"
@@ -121,159 +124,150 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
</div> </div>
</div> </div>
{/* Temperature Monitoring */} {/* Temperature Monitoring Section */}
<div> <div className="divide-y divide-[var(--border-primary)]">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <SettingRow
<Thermometer className="w-4 h-4 mr-2" /> label="Monitorización de Temperatura"
Monitorización de Temperatura description="Enable temperature tracking for inventory items"
</h4> icon={<Thermometer className="w-4 h-4" />}
<div className="space-y-4 pl-6"> type="toggle"
<div className="flex items-center gap-2"> checked={settings.temperature_monitoring_enabled}
<input onToggle={handleToggleChange('temperature_monitoring_enabled')}
type="checkbox" disabled={disabled}
id="temperature_monitoring_enabled" helpText="When enabled, system will monitor and alert on temperature deviations"
checked={settings.temperature_monitoring_enabled} />
onChange={handleChange('temperature_monitoring_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="temperature_monitoring_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar monitorización de temperatura
</label>
</div>
{settings.temperature_monitoring_enabled && ( {settings.temperature_monitoring_enabled && (
<> <>
{/* Refrigeration */} {/* Refrigeration Settings */}
<div> <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<label className="block text-xs font-medium text-[var(--text-tertiary)] mb-2"> <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
Refrigeración (°C) Refrigeración (°C)
</label> </h5>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Temperatura Mínima" label="Temperatura Mínima"
value={settings.refrigeration_temp_min} value={settings.refrigeration_temp_min}
onChange={handleChange('refrigeration_temp_min')} onChange={handleChange('refrigeration_temp_min')}
disabled={disabled} disabled={disabled}
min={-5} min={-5}
max={10} max={10}
step={0.5} step={0.5}
placeholder="1.0" placeholder="1.0"
/> />
<Input <Input
type="number" type="number"
label="Temperatura Máxima" label="Temperatura Máxima"
value={settings.refrigeration_temp_max} value={settings.refrigeration_temp_max}
onChange={handleChange('refrigeration_temp_max')} onChange={handleChange('refrigeration_temp_max')}
disabled={disabled} disabled={disabled}
min={-5} min={-5}
max={10} max={10}
step={0.5} step={0.5}
placeholder="4.0" placeholder="4.0"
/> />
</div>
</div> </div>
</div>
{/* Freezer */} {/* Freezer Settings */}
<div> <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<label className="block text-xs font-medium text-[var(--text-tertiary)] mb-2"> <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
Congelador (°C) Congelador (°C)
</label> </h5>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Temperatura Mínima" label="Temperatura Mínima"
value={settings.freezer_temp_min} value={settings.freezer_temp_min}
onChange={handleChange('freezer_temp_min')} onChange={handleChange('freezer_temp_min')}
disabled={disabled} disabled={disabled}
min={-30} min={-30}
max={0} max={0}
step={1} step={1}
placeholder="-20.0" placeholder="-20.0"
/> />
<Input <Input
type="number" type="number"
label="Temperatura Máxima" label="Temperatura Máxima"
value={settings.freezer_temp_max} value={settings.freezer_temp_max}
onChange={handleChange('freezer_temp_max')} onChange={handleChange('freezer_temp_max')}
disabled={disabled} disabled={disabled}
min={-30} min={-30}
max={0} max={0}
step={1} step={1}
placeholder="-15.0" placeholder="-15.0"
/> />
</div>
</div> </div>
</div>
{/* Room Temperature */} {/* Room Temperature Settings */}
<div> <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<label className="block text-xs font-medium text-[var(--text-tertiary)] mb-2"> <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
Temperatura Ambiente (°C) Temperatura Ambiente (°C)
</label> </h5>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Temperatura Mínima" label="Temperatura Mínima"
value={settings.room_temp_min} value={settings.room_temp_min}
onChange={handleChange('room_temp_min')} onChange={handleChange('room_temp_min')}
disabled={disabled} disabled={disabled}
min={10} min={10}
max={35} max={35}
step={1} step={1}
placeholder="18.0" placeholder="18.0"
/> />
<Input <Input
type="number" type="number"
label="Temperatura Máxima" label="Temperatura Máxima"
value={settings.room_temp_max} value={settings.room_temp_max}
onChange={handleChange('room_temp_max')} onChange={handleChange('room_temp_max')}
disabled={disabled} disabled={disabled}
min={10} min={10}
max={35} max={35}
step={1} step={1}
placeholder="25.0" placeholder="25.0"
/> />
</div>
</div> </div>
</div>
{/* Alert Timing */} {/* Alert Timing */}
<div> <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-xs font-medium text-[var(--text-tertiary)] mb-2 flex items-center"> <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4 flex items-center">
<AlertCircle className="w-3 h-3 mr-1" /> <AlertCircle className="w-4 h-4 mr-2" />
Alertas de Desviación Alertas de Desviación
</h5> </h5>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Desviación Normal (minutos)" label="Desviación Normal (minutos)"
value={settings.temp_deviation_alert_minutes} value={settings.temp_deviation_alert_minutes}
onChange={handleChange('temp_deviation_alert_minutes')} onChange={handleChange('temp_deviation_alert_minutes')}
disabled={disabled} disabled={disabled}
min={1} min={1}
max={60} max={60}
step={1} step={1}
placeholder="15" placeholder="15"
/> />
<Input <Input
type="number" type="number"
label="Desviación Crítica (minutos)" label="Desviación Crítica (minutos)"
value={settings.critical_temp_deviation_minutes} value={settings.critical_temp_deviation_minutes}
onChange={handleChange('critical_temp_deviation_minutes')} onChange={handleChange('critical_temp_deviation_minutes')}
disabled={disabled} disabled={disabled}
min={1} min={1}
max={30} max={30}
step={1} step={1}
placeholder="5" placeholder="5"
/> />
</div>
</div> </div>
</> </div>
)} </>
</div> )}
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Bell, MessageSquare, Mail, AlertCircle, Globe } from 'lucide-react'; import { Bell, MessageSquare, Mail, AlertCircle, Globe, Info } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Input, SettingSection, SettingRow, Select } from '../../../../../components/ui';
import type { NotificationSettings } from '../../../../../api/types/settings'; import type { NotificationSettings } from '../../../../../api/types/settings';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -25,7 +25,18 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
onChange({ ...settings, [field]: value }); onChange({ ...settings, [field]: value });
}; };
const handleChannelChange = (field: 'po_notification_channels' | 'inventory_alert_channels' | 'production_alert_channels' | 'forecast_alert_channels', channel: string) => { const handleToggleChange = (field: keyof NotificationSettings) => (checked: boolean) => {
onChange({ ...settings, [field]: checked });
};
const handleSelectChange = (field: keyof NotificationSettings) => (value: string) => {
onChange({ ...settings, [field]: value });
};
const handleChannelChange = (
field: 'po_notification_channels' | 'inventory_alert_channels' | 'production_alert_channels' | 'forecast_alert_channels',
channel: string
) => {
const currentChannels = settings[field]; const currentChannels = settings[field];
const newChannels = currentChannels.includes(channel) const newChannels = currentChannels.includes(channel)
? currentChannels.filter(c => c !== channel) ? currentChannels.filter(c => c !== channel)
@@ -33,140 +44,128 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
onChange({ ...settings, [field]: newChannels }); onChange({ ...settings, [field]: newChannels });
}; };
const apiVersionOptions = [
{ value: 'v18.0', label: 'v18.0' },
{ value: 'v19.0', label: 'v19.0' },
{ value: 'v20.0', label: 'v20.0' }
];
const languageOptions = [
{ value: 'es', label: 'Español' },
{ value: 'eu', label: 'Euskara' },
{ value: 'en', label: 'English' }
];
return ( return (
<Card className="p-6"> <SettingSection
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> title={t('notification.title')}
<Bell className="w-5 h-5 mr-2 text-[var(--color-primary)]" /> description="Configure WhatsApp, Email, and notification preferences for your bakery"
{t('notification.title')} icon={<Bell className="w-5 h-5" />}
</h3> >
<div className="divide-y divide-[var(--border-primary)]">
<div className="space-y-6">
{/* WhatsApp Configuration */} {/* WhatsApp Configuration */}
<div> <div className="divide-y divide-[var(--border-primary)]">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <SettingRow
<MessageSquare className="w-4 h-4 mr-2" /> label={t('notification.whatsapp_enabled')}
{t('notification.whatsapp_config')} description="Enable WhatsApp notifications for your bakery"
</h4> icon={<MessageSquare className="w-4 h-4" />}
<div className="space-y-4 pl-6"> type="toggle"
<div className="flex items-center gap-2"> checked={settings.whatsapp_enabled}
<input onToggle={handleToggleChange('whatsapp_enabled')}
type="checkbox" disabled={disabled}
id="whatsapp_enabled" helpText="Configure WhatsApp Business API for notifications"
checked={settings.whatsapp_enabled} />
onChange={handleChange('whatsapp_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="whatsapp_enabled" className="text-sm text-[var(--text-secondary)]">
{t('notification.whatsapp_enabled')}
</label>
</div>
{settings.whatsapp_enabled && ( {settings.whatsapp_enabled && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4"> <>
<Input <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
label={t('notification.whatsapp_phone_number_id')} <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
value={settings.whatsapp_phone_number_id} WhatsApp Business API Configuration
onChange={handleChange('whatsapp_phone_number_id')} </h5>
disabled={disabled} <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
placeholder="123456789012345" <Input
helperText={t('notification.whatsapp_phone_number_id_help')} label={t('notification.whatsapp_phone_number_id')}
/> value={settings.whatsapp_phone_number_id}
onChange={handleChange('whatsapp_phone_number_id')}
disabled={disabled}
placeholder="123456789012345"
helperText={t('notification.whatsapp_phone_number_id_help')}
/>
<Input <Input
type="password" type="password"
label={t('notification.whatsapp_access_token')} label={t('notification.whatsapp_access_token')}
value={settings.whatsapp_access_token} value={settings.whatsapp_access_token}
onChange={handleChange('whatsapp_access_token')} onChange={handleChange('whatsapp_access_token')}
disabled={disabled} disabled={disabled}
placeholder="EAAxxxxxxxx" placeholder="EAAxxxxxxxx"
helperText={t('notification.whatsapp_access_token_help')} helperText={t('notification.whatsapp_access_token_help')}
/> />
<Input <Input
label={t('notification.whatsapp_business_account_id')} label={t('notification.whatsapp_business_account_id')}
value={settings.whatsapp_business_account_id} value={settings.whatsapp_business_account_id}
onChange={handleChange('whatsapp_business_account_id')} onChange={handleChange('whatsapp_business_account_id')}
disabled={disabled} disabled={disabled}
placeholder="987654321098765" placeholder="987654321098765"
helperText={t('notification.whatsapp_business_account_id_help')} helperText={t('notification.whatsapp_business_account_id_help')}
/> />
<div> <Select
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"> label={t('notification.whatsapp_api_version')}
{t('notification.whatsapp_api_version')} options={apiVersionOptions}
</label>
<select
value={settings.whatsapp_api_version} value={settings.whatsapp_api_version}
onChange={handleChange('whatsapp_api_version')} onChange={handleSelectChange('whatsapp_api_version')}
disabled={disabled} disabled={disabled}
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg text-sm bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)]" />
>
<option value="v18.0">v18.0</option>
<option value="v19.0">v19.0</option>
<option value="v20.0">v20.0</option>
</select>
</div>
<div> <Select
<label className="block text-sm font-medium text-[var(--text-secondary)] mb-2"> label={t('notification.whatsapp_default_language')}
{t('notification.whatsapp_default_language')} options={languageOptions}
</label>
<select
value={settings.whatsapp_default_language} value={settings.whatsapp_default_language}
onChange={handleChange('whatsapp_default_language')} onChange={handleSelectChange('whatsapp_default_language')}
disabled={disabled} disabled={disabled}
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg text-sm bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)]" />
>
<option value="es">Español</option>
<option value="eu">Euskara</option>
<option value="en">English</option>
</select>
</div> </div>
</div>
)}
{settings.whatsapp_enabled && ( {/* WhatsApp Setup Info */}
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800"> <div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<AlertCircle className="w-4 h-4 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" /> <Info className="w-4 h-4 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<div className="text-xs text-blue-700 dark:text-blue-300"> <div className="text-xs text-blue-700 dark:text-blue-300">
<p className="font-semibold mb-1">{t('notification.whatsapp_setup_note')}</p> <p className="font-semibold mb-1">{t('notification.whatsapp_setup_note')}</p>
<ul className="list-disc list-inside space-y-1"> <ul className="list-disc list-inside space-y-1">
<li>{t('notification.whatsapp_setup_step1')}</li> <li>{t('notification.whatsapp_setup_step1')}</li>
<li>{t('notification.whatsapp_setup_step2')}</li> <li>{t('notification.whatsapp_setup_step2')}</li>
<li>{t('notification.whatsapp_setup_step3')}</li> <li>{t('notification.whatsapp_setup_step3')}</li>
</ul> </ul>
</div>
</div> </div>
</div> </div>
</div> </div>
)} </>
</div> )}
</div> </div>
{/* Email Configuration */} {/* Email Configuration */}
<div> <div className="divide-y divide-[var(--border-primary)]">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <SettingRow
<Mail className="w-4 h-4 mr-2" /> label={t('notification.email_enabled')}
{t('notification.email_config')} description="Enable email notifications for your bakery"
</h4> icon={<Mail className="w-4 h-4" />}
<div className="space-y-4 pl-6"> type="toggle"
<div className="flex items-center gap-2"> checked={settings.email_enabled}
<input onToggle={handleToggleChange('email_enabled')}
type="checkbox" disabled={disabled}
id="email_enabled" helpText="Configure email sender details for notifications"
checked={settings.email_enabled} />
onChange={handleChange('email_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="email_enabled" className="text-sm text-[var(--text-secondary)]">
{t('notification.email_enabled')}
</label>
</div>
{settings.email_enabled && ( {settings.email_enabled && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4"> <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
Email Sender Configuration
</h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="email" type="email"
label={t('notification.email_from_address')} label={t('notification.email_from_address')}
@@ -193,184 +192,206 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
placeholder="info@yourbakery.com" placeholder="info@yourbakery.com"
/> />
</div> </div>
)} </div>
</div> )}
</div> </div>
{/* Notification Preferences */} {/* Notification Preferences Section Header */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-2 flex items-center">
<Globe className="w-4 h-4 mr-2" /> <Globe className="w-4 h-4 mr-2" />
{t('notification.preferences')} {t('notification.preferences')}
</h4> </h4>
<div className="space-y-4 pl-6"> <p className="text-xs text-[var(--text-tertiary)]">
{/* PO Notifications */} Configure which notifications you want to receive and through which channels
<div className="space-y-2"> </p>
<div className="flex items-center gap-2"> </div>
<input
type="checkbox"
id="enable_po_notifications"
checked={settings.enable_po_notifications}
onChange={handleChange('enable_po_notifications')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="enable_po_notifications" className="text-sm font-medium text-[var(--text-secondary)]">
{t('notification.enable_po_notifications')}
</label>
</div>
{settings.enable_po_notifications && (
<div className="pl-6 flex gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.po_notification_channels.includes('email')}
onChange={() => handleChannelChange('po_notification_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.po_notification_channels.includes('whatsapp')}
onChange={() => handleChannelChange('po_notification_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
WhatsApp
</label>
</div>
)}
</div>
{/* Inventory Alerts */} {/* PO Notifications */}
<div className="space-y-2"> <div className="divide-y divide-[var(--border-primary)]">
<div className="flex items-center gap-2"> <SettingRow
<input label={t('notification.enable_po_notifications')}
type="checkbox" description="Receive notifications for purchase order updates"
id="enable_inventory_alerts" type="toggle"
checked={settings.enable_inventory_alerts} checked={settings.enable_po_notifications}
onChange={handleChange('enable_inventory_alerts')} onToggle={handleToggleChange('enable_po_notifications')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Get notified when POs are created, approved, or delivered"
/> />
<label htmlFor="enable_inventory_alerts" className="text-sm font-medium text-[var(--text-secondary)]">
{t('notification.enable_inventory_alerts')}
</label>
</div>
{settings.enable_inventory_alerts && (
<div className="pl-6 flex gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.inventory_alert_channels.includes('email')}
onChange={() => handleChannelChange('inventory_alert_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.inventory_alert_channels.includes('whatsapp')}
onChange={() => handleChannelChange('inventory_alert_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
WhatsApp
</label>
</div>
)}
</div>
{/* Production Alerts */} {settings.enable_po_notifications && (
<div className="space-y-2"> <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<div className="flex items-center gap-2"> <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-3">
<input Notification Channels
type="checkbox" </h5>
id="enable_production_alerts" <div className="flex flex-wrap gap-4">
checked={settings.enable_production_alerts} <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
onChange={handleChange('enable_production_alerts')} <input
disabled={disabled} type="checkbox"
className="rounded border-[var(--border-primary)]" checked={settings.po_notification_channels.includes('email')}
/> onChange={() => handleChannelChange('po_notification_channels', 'email')}
<label htmlFor="enable_production_alerts" className="text-sm font-medium text-[var(--text-secondary)]"> disabled={disabled}
{t('notification.enable_production_alerts')} className="rounded border-[var(--border-primary)]"
/>
<Mail className="w-4 h-4" />
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.po_notification_channels.includes('whatsapp')}
onChange={() => handleChannelChange('po_notification_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
<MessageSquare className="w-4 h-4" />
WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label> </label>
</div> </div>
{settings.enable_production_alerts && (
<div className="pl-6 flex gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.production_alert_channels.includes('email')}
onChange={() => handleChannelChange('production_alert_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.production_alert_channels.includes('whatsapp')}
onChange={() => handleChannelChange('production_alert_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
WhatsApp
</label>
</div>
)}
</div> </div>
)}
</div>
{/* Forecast Alerts */} {/* Inventory Alerts */}
<div className="space-y-2"> <div className="divide-y divide-[var(--border-primary)]">
<div className="flex items-center gap-2"> <SettingRow
<input label={t('notification.enable_inventory_alerts')}
type="checkbox" description="Receive alerts for low stock and inventory issues"
id="enable_forecast_alerts" type="toggle"
checked={settings.enable_forecast_alerts} checked={settings.enable_inventory_alerts}
onChange={handleChange('enable_forecast_alerts')} onToggle={handleToggleChange('enable_inventory_alerts')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Get notified about low stock, expiring items, and inventory problems"
/> />
<label htmlFor="enable_forecast_alerts" className="text-sm font-medium text-[var(--text-secondary)]">
{t('notification.enable_forecast_alerts')} {settings.enable_inventory_alerts && (
<div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-3">
Notification Channels
</h5>
<div className="flex flex-wrap gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.inventory_alert_channels.includes('email')}
onChange={() => handleChannelChange('inventory_alert_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<Mail className="w-4 h-4" />
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.inventory_alert_channels.includes('whatsapp')}
onChange={() => handleChannelChange('inventory_alert_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
<MessageSquare className="w-4 h-4" />
WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label> </label>
</div> </div>
{settings.enable_forecast_alerts && (
<div className="pl-6 flex gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.forecast_alert_channels.includes('email')}
onChange={() => handleChannelChange('forecast_alert_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.forecast_alert_channels.includes('whatsapp')}
onChange={() => handleChannelChange('forecast_alert_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
WhatsApp
</label>
</div>
)}
</div> </div>
</div> )}
</div>
{/* Production Alerts */}
<div className="divide-y divide-[var(--border-primary)]">
<SettingRow
label={t('notification.enable_production_alerts')}
description="Receive alerts for production schedule and capacity issues"
type="toggle"
checked={settings.enable_production_alerts}
onToggle={handleToggleChange('enable_production_alerts')}
disabled={disabled}
helpText="Get notified about production delays, capacity warnings, and quality issues"
/>
{settings.enable_production_alerts && (
<div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-3">
Notification Channels
</h5>
<div className="flex flex-wrap gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.production_alert_channels.includes('email')}
onChange={() => handleChannelChange('production_alert_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<Mail className="w-4 h-4" />
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.production_alert_channels.includes('whatsapp')}
onChange={() => handleChannelChange('production_alert_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
<MessageSquare className="w-4 h-4" />
WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label>
</div>
</div>
)}
</div>
{/* Forecast Alerts */}
<div className="divide-y divide-[var(--border-primary)]">
<SettingRow
label={t('notification.enable_forecast_alerts')}
description="Receive alerts for demand forecast and predictions"
type="toggle"
checked={settings.enable_forecast_alerts}
onToggle={handleToggleChange('enable_forecast_alerts')}
disabled={disabled}
helpText="Get notified about demand predictions and forecast updates"
/>
{settings.enable_forecast_alerts && (
<div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-3">
Notification Channels
</h5>
<div className="flex flex-wrap gap-4">
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.forecast_alert_channels.includes('email')}
onChange={() => handleChannelChange('forecast_alert_channels', 'email')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<Mail className="w-4 h-4" />
Email
</label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input
type="checkbox"
checked={settings.forecast_alert_channels.includes('whatsapp')}
onChange={() => handleChannelChange('forecast_alert_channels', 'whatsapp')}
disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]"
/>
<MessageSquare className="w-4 h-4" />
WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label>
</div>
</div>
)}
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ShoppingBag, Tag, Clock, TrendingUp, MapPin } from 'lucide-react'; import { ShoppingBag, Tag, Clock, TrendingUp, MapPin, Info } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Input, SettingSection, SettingRow } from '../../../../../components/ui';
import type { OrderSettings } from '../../../../../api/types/settings'; import type { OrderSettings } from '../../../../../api/types/settings';
interface OrderSettingsCardProps { interface OrderSettingsCardProps {
@@ -23,118 +23,106 @@ const OrderSettingsCard: React.FC<OrderSettingsCardProps> = ({
onChange({ ...settings, [field]: value }); onChange({ ...settings, [field]: value });
}; };
return ( const handleToggleChange = (field: keyof OrderSettings) => (checked: boolean) => {
<Card className="p-6"> onChange({ ...settings, [field]: checked });
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> };
<ShoppingBag className="w-5 h-5 mr-2 text-[var(--color-primary)]" />
Pedidos y Reglas de Negocio
</h3>
<div className="space-y-6"> return (
<SettingSection
title="Pedidos y Reglas de Negocio"
description="Configure order discounts, pricing rules, and delivery settings"
icon={<ShoppingBag className="w-5 h-5" />}
>
<div className="divide-y divide-[var(--border-primary)]">
{/* Discount & Pricing */} {/* Discount & Pricing */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Tag className="w-4 h-4 mr-2" /> <Tag className="w-4 h-4 mr-2" />
Descuentos y Precios Descuentos y Precios
</h4> </h4>
<div className="space-y-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <Input
<Input type="number"
type="number" label="Descuento Máximo (%)"
label="Descuento Máximo (%)" value={settings.max_discount_percentage}
value={settings.max_discount_percentage} onChange={handleChange('max_discount_percentage')}
onChange={handleChange('max_discount_percentage')} disabled={disabled}
disabled={disabled} min={0}
min={0} max={100}
max={100} step={1}
step={1} placeholder="50.0"
placeholder="50.0" helperText="Porcentaje máximo de descuento permitido"
helperText="Porcentaje máximo de descuento permitido en pedidos" />
/>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="discount_enabled"
checked={settings.discount_enabled}
onChange={handleChange('discount_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="discount_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar descuentos en pedidos
</label>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="dynamic_pricing_enabled"
checked={settings.dynamic_pricing_enabled}
onChange={handleChange('dynamic_pricing_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="dynamic_pricing_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar precios dinámicos
</label>
</div>
</div>
</div> </div>
</div> </div>
<SettingRow
label="Habilitar descuentos en pedidos"
description="Allow discounts to be applied to orders"
icon={<Tag className="w-4 h-4" />}
type="toggle"
checked={settings.discount_enabled}
onToggle={handleToggleChange('discount_enabled')}
disabled={disabled}
helpText="When enabled, discounts can be applied within the maximum limit"
/>
<SettingRow
label="Habilitar precios dinámicos"
description="Automatically adjust prices based on demand and inventory"
icon={<TrendingUp className="w-4 h-4" />}
type="toggle"
checked={settings.dynamic_pricing_enabled}
onToggle={handleToggleChange('dynamic_pricing_enabled')}
disabled={disabled}
helpText="AI-powered dynamic pricing based on market conditions"
/>
{/* Delivery Settings */} {/* Delivery Settings */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<MapPin className="w-4 h-4 mr-2" /> <MapPin className="w-4 h-4 mr-2" />
Configuración de Entrega Configuración de Entrega
</h4> </h4>
<div className="space-y-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <Input
<Input type="number"
type="number" label="Ventana de Entrega Predeterminada (horas)"
label="Ventana de Entrega Predeterminada (horas)" value={settings.default_delivery_window_hours}
value={settings.default_delivery_window_hours} onChange={handleChange('default_delivery_window_hours')}
onChange={handleChange('default_delivery_window_hours')} disabled={disabled}
disabled={disabled} min={1}
min={1} max={168}
max={168} step={1}
step={1} placeholder="48"
placeholder="48" helperText="Tiempo predeterminado para entrega"
helperText="Tiempo predeterminado para la entrega de pedidos" />
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="delivery_tracking_enabled"
checked={settings.delivery_tracking_enabled}
onChange={handleChange('delivery_tracking_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="delivery_tracking_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar seguimiento de entregas
</label>
</div>
</div> </div>
</div> </div>
<SettingRow
label="Habilitar seguimiento de entregas"
description="Allow customers to track their deliveries in real-time"
icon={<MapPin className="w-4 h-4" />}
type="toggle"
checked={settings.delivery_tracking_enabled}
onToggle={handleToggleChange('delivery_tracking_enabled')}
disabled={disabled}
helpText="Enables real-time delivery tracking for customers"
/>
{/* Info Box */} {/* Info Box */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4"> <div className="p-4 sm:p-6 bg-blue-50 dark:bg-blue-900/20">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<TrendingUp className="w-5 h-5 text-blue-600 mt-0.5" /> <Info className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<div className="flex-1"> <div className="flex-1">
<h5 className="text-sm font-semibold text-blue-900 mb-1"> <h5 className="text-sm font-semibold text-blue-900 dark:text-blue-100 mb-1">
Reglas de Negocio Reglas de Negocio
</h5> </h5>
<p className="text-xs text-blue-700 mb-2"> <p className="text-xs text-blue-700 dark:text-blue-300 mb-2">
Estos ajustes controlan las reglas de negocio que se aplican a los pedidos. Estos ajustes controlan las reglas de negocio que se aplican a los pedidos.
</p> </p>
<ul className="text-xs text-blue-700 space-y-1 list-disc list-inside"> <ul className="text-xs text-blue-700 dark:text-blue-300 space-y-1 list-disc list-inside">
<li><strong>Precios dinámicos:</strong> Ajusta automáticamente los precios según demanda, inventario y otros factores</li> <li><strong>Precios dinámicos:</strong> Ajusta automáticamente los precios según demanda, inventario y otros factores</li>
<li><strong>Descuentos:</strong> Permite aplicar descuentos a productos y pedidos dentro del límite establecido</li> <li><strong>Descuentos:</strong> Permite aplicar descuentos a productos y pedidos dentro del límite establecido</li>
<li><strong>Seguimiento de entregas:</strong> Permite a los clientes rastrear sus pedidos en tiempo real</li> <li><strong>Seguimiento de entregas:</strong> Permite a los clientes rastrear sus pedidos en tiempo real</li>
@@ -143,7 +131,7 @@ const OrderSettingsCard: React.FC<OrderSettingsCardProps> = ({
</div> </div>
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Smartphone, RefreshCw, Clock } from 'lucide-react'; import { Smartphone, RefreshCw, Clock, Info } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Input, SettingSection, SettingRow } from '../../../../../components/ui';
import type { POSSettings } from '../../../../../api/types/settings'; import type { POSSettings } from '../../../../../api/types/settings';
interface POSSettingsCardProps { interface POSSettingsCardProps {
@@ -23,21 +23,24 @@ const POSSettingsCard: React.FC<POSSettingsCardProps> = ({
onChange({ ...settings, [field]: value }); onChange({ ...settings, [field]: value });
}; };
return ( const handleToggleChange = (field: keyof POSSettings) => (checked: boolean) => {
<Card className="p-6"> onChange({ ...settings, [field]: checked });
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> };
<Smartphone className="w-5 h-5 mr-2 text-[var(--color-primary)]" />
Punto de Venta (POS)
</h3>
<div className="space-y-6"> return (
<SettingSection
title="Punto de Venta (POS)"
description="Configure POS system sync settings and integration options"
icon={<Smartphone className="w-5 h-5" />}
>
<div className="divide-y divide-[var(--border-primary)]">
{/* Sync Settings */} {/* Sync Settings */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<RefreshCw className="w-4 h-4 mr-2" /> <RefreshCw className="w-4 h-4 mr-2" />
Sincronización Sincronización
</h4> </h4>
<div className="space-y-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Intervalo de Sincronización (minutos)" label="Intervalo de Sincronización (minutos)"
@@ -48,54 +51,47 @@ const POSSettingsCard: React.FC<POSSettingsCardProps> = ({
max={60} max={60}
step={1} step={1}
placeholder="5" placeholder="5"
helperText="Frecuencia con la que se sincroniza el POS con el sistema central" helperText="Frecuencia con la que se sincroniza el POS"
/> />
<div className="space-y-3">
<div className="flex items-center gap-2">
<input
type="checkbox"
id="auto_sync_products"
checked={settings.auto_sync_products}
onChange={handleChange('auto_sync_products')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="auto_sync_products" className="text-sm text-[var(--text-secondary)]">
Sincronización automática de productos
</label>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="auto_sync_transactions"
checked={settings.auto_sync_transactions}
onChange={handleChange('auto_sync_transactions')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="auto_sync_transactions" className="text-sm text-[var(--text-secondary)]">
Sincronización automática de transacciones
</label>
</div>
</div>
</div> </div>
</div> </div>
{/* Auto-sync Options */}
<SettingRow
label="Sincronización automática de productos"
description="Automatically sync product changes to POS terminals"
icon={<RefreshCw className="w-4 h-4" />}
type="toggle"
checked={settings.auto_sync_products}
onToggle={handleToggleChange('auto_sync_products')}
disabled={disabled}
helpText="When enabled, product updates are immediately pushed to POS"
/>
<SettingRow
label="Sincronización automática de transacciones"
description="Automatically sync transactions from POS terminals"
icon={<RefreshCw className="w-4 h-4" />}
type="toggle"
checked={settings.auto_sync_transactions}
onToggle={handleToggleChange('auto_sync_transactions')}
disabled={disabled}
helpText="When enabled, sales are automatically synced from POS"
/>
{/* Info Box */} {/* Info Box */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4"> <div className="p-4 sm:p-6 bg-blue-50 dark:bg-blue-900/20">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<Smartphone className="w-5 h-5 text-blue-600 mt-0.5" /> <Info className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<div className="flex-1"> <div className="flex-1">
<h5 className="text-sm font-semibold text-blue-900 mb-1"> <h5 className="text-sm font-semibold text-blue-900 dark:text-blue-100 mb-1">
Integración POS Integración POS
</h5> </h5>
<p className="text-xs text-blue-700 mb-2"> <p className="text-xs text-blue-700 dark:text-blue-300 mb-2">
Estos ajustes controlan cómo se sincroniza la información entre el sistema central Estos ajustes controlan cómo se sincroniza la información entre el sistema central
y los terminales de punto de venta. y los terminales de punto de venta.
</p> </p>
<ul className="text-xs text-blue-700 space-y-1 list-disc list-inside"> <ul className="text-xs text-blue-700 dark:text-blue-300 space-y-1 list-disc list-inside">
<li>Un intervalo más corto mantiene los datos más actualizados pero consume más recursos</li> <li>Un intervalo más corto mantiene los datos más actualizados pero consume más recursos</li>
<li>La sincronización automática garantiza que los cambios se reflejen inmediatamente</li> <li>La sincronización automática garantiza que los cambios se reflejen inmediatamente</li>
<li>Desactivar la sincronización automática requiere sincronización manual</li> <li>Desactivar la sincronización automática requiere sincronización manual</li>
@@ -104,7 +100,7 @@ const POSSettingsCard: React.FC<POSSettingsCardProps> = ({
</div> </div>
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { ShoppingCart, TrendingUp, Clock, AlertTriangle, Brain } from 'lucide-react'; import { ShoppingCart, TrendingUp, Clock, AlertTriangle, Brain } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Input, SettingSection, SettingRow } from '../../../../../components/ui';
import type { ProcurementSettings } from '../../../../../api/types/settings'; import type { ProcurementSettings } from '../../../../../api/types/settings';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -26,96 +26,91 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
onChange({ ...settings, [field]: value }); onChange({ ...settings, [field]: value });
}; };
const handleToggleChange = (field: keyof ProcurementSettings) => (checked: boolean) => {
onChange({ ...settings, [field]: checked });
};
return ( return (
<Card className="p-6"> <SettingSection
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> title={t('procurement.title')}
<ShoppingCart className="w-5 h-5 mr-2 text-[var(--color-primary)]" /> description="Configure automatic approval rules, planning parameters, and smart procurement options"
{t('procurement.title')} icon={<ShoppingCart className="w-5 h-5" />}
</h3> >
<div className="divide-y divide-[var(--border-primary)]">
<div className="space-y-6">
{/* Auto-Approval Settings */} {/* Auto-Approval Settings */}
<div> <div className="divide-y divide-[var(--border-primary)]">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <SettingRow
<TrendingUp className="w-4 h-4 mr-2" /> label={t('procurement.auto_approve_enabled')}
{t('procurement.auto_approval')} description="Automatically approve purchase orders that meet criteria"
</h4> icon={<TrendingUp className="w-4 h-4" />}
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> type="toggle"
<div className="flex items-center gap-2 md:col-span-2 xl:col-span-3"> checked={settings.auto_approve_enabled}
<input onToggle={handleToggleChange('auto_approve_enabled')}
type="checkbox" disabled={disabled}
id="auto_approve_enabled" helpText="When enabled, POs below threshold with good suppliers are auto-approved"
checked={settings.auto_approve_enabled} />
onChange={handleChange('auto_approve_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="auto_approve_enabled" className="text-sm text-[var(--text-secondary)]">
{t('procurement.auto_approve_enabled')}
</label>
</div>
<Input {settings.auto_approve_enabled && (
type="number" <>
label={t('procurement.auto_approve_threshold')} <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
value={settings.auto_approve_threshold_eur} <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
onChange={handleChange('auto_approve_threshold_eur')} Auto-Approval Thresholds
disabled={disabled || !settings.auto_approve_enabled} </h5>
min={0} <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
max={10000} <Input
step={50} type="number"
placeholder="500.0" label={t('procurement.auto_approve_threshold')}
/> value={settings.auto_approve_threshold_eur}
onChange={handleChange('auto_approve_threshold_eur')}
disabled={disabled}
min={0}
max={10000}
step={50}
placeholder="500.0"
/>
<Input <Input
type="number" type="number"
label={t('procurement.min_supplier_score')} label={t('procurement.min_supplier_score')}
value={settings.auto_approve_min_supplier_score} value={settings.auto_approve_min_supplier_score}
onChange={handleChange('auto_approve_min_supplier_score')} onChange={handleChange('auto_approve_min_supplier_score')}
disabled={disabled || !settings.auto_approve_enabled} disabled={disabled}
min={0} min={0}
max={1} max={1}
step={0.01} step={0.01}
placeholder="0.80" placeholder="0.80"
/> />
</div>
</div>
<div className="flex items-center gap-2"> <SettingRow
<input label={t('procurement.require_approval_new_suppliers')}
type="checkbox" description="Always require manual approval for new suppliers"
id="require_approval_new_suppliers" type="toggle"
checked={settings.require_approval_new_suppliers} checked={settings.require_approval_new_suppliers}
onChange={handleChange('require_approval_new_suppliers')} onToggle={handleToggleChange('require_approval_new_suppliers')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]"
/> />
<label htmlFor="require_approval_new_suppliers" className="text-sm text-[var(--text-secondary)]">
{t('procurement.require_approval_new_suppliers')}
</label>
</div>
<div className="flex items-center gap-2"> <SettingRow
<input label={t('procurement.require_approval_critical_items')}
type="checkbox" description="Always require manual approval for critical items"
id="require_approval_critical_items" type="toggle"
checked={settings.require_approval_critical_items} checked={settings.require_approval_critical_items}
onChange={handleChange('require_approval_critical_items')} onToggle={handleToggleChange('require_approval_critical_items')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]"
/> />
<label htmlFor="require_approval_critical_items" className="text-sm text-[var(--text-secondary)]"> </>
{t('procurement.require_approval_critical_items')} )}
</label>
</div>
</div>
</div> </div>
{/* Planning & Forecasting */} {/* Planning & Forecasting */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Clock className="w-4 h-4 mr-2" /> <Clock className="w-4 h-4 mr-2" />
{t('procurement.planning')} {t('procurement.planning')}
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<Input <Input
type="number" type="number"
label={t('procurement.lead_time_days')} label={t('procurement.lead_time_days')}
@@ -155,12 +150,12 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
</div> </div>
{/* Approval Workflow */} {/* Approval Workflow */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<AlertTriangle className="w-4 h-4 mr-2" /> <AlertTriangle className="w-4 h-4 mr-2" />
{t('procurement.workflow')} {t('procurement.workflow')}
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label={t('procurement.approval_reminder_hours')} label={t('procurement.approval_reminder_hours')}
@@ -187,111 +182,70 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
</div> </div>
</div> </div>
{/* Smart Procurement Calculation */} {/* Smart Procurement Options */}
<div className="border-t border-[var(--border-primary)] pt-6"> <div className="divide-y divide-[var(--border-primary)]">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <div className="p-4 sm:p-6">
<Brain className="w-4 h-4 mr-2" /> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-2 flex items-center">
{t('procurement.smart_procurement')} <Brain className="w-4 h-4 mr-2" />
</h4> {t('procurement.smart_procurement')}
<div className="space-y-3 pl-6"> </h4>
<div className="flex items-start gap-2"> <p className="text-xs text-[var(--text-tertiary)] mb-4">
<input Intelligent rules for optimizing procurement decisions
type="checkbox" </p>
id="use_reorder_rules"
checked={settings.use_reorder_rules}
onChange={handleChange('use_reorder_rules')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="use_reorder_rules" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.use_reorder_rules')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.use_reorder_rules_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="economic_rounding"
checked={settings.economic_rounding}
onChange={handleChange('economic_rounding')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="economic_rounding" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.economic_rounding')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.economic_rounding_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="respect_storage_limits"
checked={settings.respect_storage_limits}
onChange={handleChange('respect_storage_limits')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="respect_storage_limits" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.respect_storage_limits')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.respect_storage_limits_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="use_supplier_minimums"
checked={settings.use_supplier_minimums}
onChange={handleChange('use_supplier_minimums')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="use_supplier_minimums" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.use_supplier_minimums')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.use_supplier_minimums_desc')}
</span>
</div>
</div>
<div className="flex items-start gap-2">
<input
type="checkbox"
id="optimize_price_tiers"
checked={settings.optimize_price_tiers}
onChange={handleChange('optimize_price_tiers')}
disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5"
/>
<div className="flex flex-col">
<label htmlFor="optimize_price_tiers" className="text-sm font-medium text-[var(--text-secondary)]">
{t('procurement.optimize_price_tiers')}
</label>
<span className="text-xs text-[var(--text-tertiary)] mt-0.5">
{t('procurement.optimize_price_tiers_desc')}
</span>
</div>
</div>
</div> </div>
<SettingRow
label={t('procurement.use_reorder_rules')}
description={t('procurement.use_reorder_rules_desc')}
type="toggle"
checked={settings.use_reorder_rules}
onToggle={handleToggleChange('use_reorder_rules')}
disabled={disabled}
helpText="Automatically calculate when to reorder based on usage patterns"
/>
<SettingRow
label={t('procurement.economic_rounding')}
description={t('procurement.economic_rounding_desc')}
type="toggle"
checked={settings.economic_rounding}
onToggle={handleToggleChange('economic_rounding')}
disabled={disabled}
helpText="Round order quantities to economical batch sizes"
/>
<SettingRow
label={t('procurement.respect_storage_limits')}
description={t('procurement.respect_storage_limits_desc')}
type="toggle"
checked={settings.respect_storage_limits}
onToggle={handleToggleChange('respect_storage_limits')}
disabled={disabled}
helpText="Ensure orders don't exceed available storage capacity"
/>
<SettingRow
label={t('procurement.use_supplier_minimums')}
description={t('procurement.use_supplier_minimums_desc')}
type="toggle"
checked={settings.use_supplier_minimums}
onToggle={handleToggleChange('use_supplier_minimums')}
disabled={disabled}
helpText="Respect minimum order quantities set by suppliers"
/>
<SettingRow
label={t('procurement.optimize_price_tiers')}
description={t('procurement.optimize_price_tiers_desc')}
type="toggle"
checked={settings.optimize_price_tiers}
onToggle={handleToggleChange('optimize_price_tiers')}
disabled={disabled}
helpText="Automatically optimize order quantities to get best price tiers"
/>
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Factory, Calendar, TrendingUp, Clock, DollarSign } from 'lucide-react'; import { Factory, Calendar, TrendingUp, Clock, DollarSign } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Input, SettingSection, SettingRow } from '../../../../../components/ui';
import type { ProductionSettings } from '../../../../../api/types/settings'; import type { ProductionSettings } from '../../../../../api/types/settings';
interface ProductionSettingsCardProps { interface ProductionSettingsCardProps {
@@ -23,21 +23,24 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
onChange({ ...settings, [field]: value }); onChange({ ...settings, [field]: value });
}; };
return ( const handleToggleChange = (field: keyof ProductionSettings) => (checked: boolean) => {
<Card className="p-6"> onChange({ ...settings, [field]: checked });
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> };
<Factory className="w-5 h-5 mr-2 text-[var(--color-primary)]" />
Producción
</h3>
<div className="space-y-6"> return (
<SettingSection
title="Producción"
description="Configure production planning, batch sizes, quality control, and cost settings"
icon={<Factory className="w-5 h-5" />}
>
<div className="divide-y divide-[var(--border-primary)]">
{/* Planning & Batch Size */} {/* Planning & Batch Size */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Calendar className="w-4 h-4 mr-2" /> <Calendar className="w-4 h-4 mr-2" />
Planificación y Lotes Planificación y Lotes
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<Input <Input
type="number" type="number"
label="Horizonte de Planificación (días)" label="Horizonte de Planificación (días)"
@@ -85,30 +88,29 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
step={1} step={1}
placeholder="10.0" placeholder="10.0"
/> />
<div className="flex items-center gap-2 md:col-span-2 xl:col-span-2">
<input
type="checkbox"
id="schedule_optimization_enabled"
checked={settings.schedule_optimization_enabled}
onChange={handleChange('schedule_optimization_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="schedule_optimization_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar optimización de horarios
</label>
</div>
</div> </div>
</div> </div>
{/* Schedule Optimization */}
<div className="divide-y divide-[var(--border-primary)]">
<SettingRow
label="Optimización de Horarios"
description="Enable intelligent schedule optimization for production batches"
type="toggle"
checked={settings.schedule_optimization_enabled}
onToggle={handleToggleChange('schedule_optimization_enabled')}
disabled={disabled}
helpText="Uses AI to optimize production schedules based on capacity and demand"
/>
</div>
{/* Capacity & Working Hours */} {/* Capacity & Working Hours */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Clock className="w-4 h-4 mr-2" /> <Clock className="w-4 h-4 mr-2" />
Capacidad y Jornada Laboral Capacidad y Jornada Laboral
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
<Input <Input
type="number" type="number"
label="Horas de Trabajo por Día" label="Horas de Trabajo por Día"
@@ -160,61 +162,59 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
</div> </div>
{/* Quality Control */} {/* Quality Control */}
<div> <div className="divide-y divide-[var(--border-primary)]">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <SettingRow
<TrendingUp className="w-4 h-4 mr-2" /> label="Verificación de Calidad"
Control de Calidad description="Enable quality checks for production batches"
</h4> icon={<TrendingUp className="w-4 h-4" />}
<div className="space-y-4 pl-6"> type="toggle"
<div className="flex items-center gap-2"> checked={settings.quality_check_enabled}
<input onToggle={handleToggleChange('quality_check_enabled')}
type="checkbox" disabled={disabled}
id="quality_check_enabled" helpText="When enabled, production batches require quality verification"
checked={settings.quality_check_enabled} />
onChange={handleChange('quality_check_enabled')}
disabled={disabled}
className="rounded border-[var(--border-primary)]"
/>
<label htmlFor="quality_check_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar verificación de calidad
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> {settings.quality_check_enabled && (
<Input <div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
type="number" <h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
label="Rendimiento Mínimo (%)" Parámetros de Calidad
value={settings.minimum_yield_percentage} </h5>
onChange={handleChange('minimum_yield_percentage')} <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
disabled={disabled || !settings.quality_check_enabled} <Input
min={50} type="number"
max={100} label="Rendimiento Mínimo (%)"
step={1} value={settings.minimum_yield_percentage}
placeholder="85.0" onChange={handleChange('minimum_yield_percentage')}
/> disabled={disabled}
min={50}
max={100}
step={1}
placeholder="85.0"
/>
<Input <Input
type="number" type="number"
label="Umbral de Puntuación de Calidad (0-10)" label="Umbral de Puntuación de Calidad (0-10)"
value={settings.quality_score_threshold} value={settings.quality_score_threshold}
onChange={handleChange('quality_score_threshold')} onChange={handleChange('quality_score_threshold')}
disabled={disabled || !settings.quality_check_enabled} disabled={disabled}
min={0} min={0}
max={10} max={10}
step={0.1} step={0.1}
placeholder="8.0" placeholder="8.0"
/> />
</div>
</div> </div>
</div> )}
</div> </div>
{/* Time Buffers */} {/* Time Buffers */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Clock className="w-4 h-4 mr-2" /> <Clock className="w-4 h-4 mr-2" />
Tiempos de Preparación Tiempos de Preparación
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Tiempo de Preparación (minutos)" label="Tiempo de Preparación (minutos)"
@@ -242,12 +242,12 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
</div> </div>
{/* Cost Settings */} {/* Cost Settings */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<DollarSign className="w-4 h-4 mr-2" /> <DollarSign className="w-4 h-4 mr-2" />
Costes Costes
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Coste Laboral por Hora (EUR)" label="Coste Laboral por Hora (EUR)"
@@ -274,7 +274,7 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
</div> </div>
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Truck, Calendar, TrendingUp, AlertTriangle, DollarSign } from 'lucide-react'; import { Truck, Calendar, TrendingUp, AlertTriangle, Info } from 'lucide-react';
import { Card, Input } from '../../../../../components/ui'; import { Input, SettingSection } from '../../../../../components/ui';
import type { SupplierSettings } from '../../../../../api/types/settings'; import type { SupplierSettings } from '../../../../../api/types/settings';
interface SupplierSettingsCardProps { interface SupplierSettingsCardProps {
@@ -22,20 +22,19 @@ const SupplierSettingsCard: React.FC<SupplierSettingsCardProps> = ({
}; };
return ( return (
<Card className="p-6"> <SettingSection
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> title="Gestión de Proveedores"
<Truck className="w-5 h-5 mr-2 text-[var(--color-primary)]" /> description="Configure supplier performance thresholds and default terms"
Gestión de Proveedores icon={<Truck className="w-5 h-5" />}
</h3> >
<div className="divide-y divide-[var(--border-primary)]">
<div className="space-y-6">
{/* Default Terms */} {/* Default Terms */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<Calendar className="w-4 h-4 mr-2" /> <Calendar className="w-4 h-4 mr-2" />
Términos Predeterminados Términos Predeterminados
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Plazo de Pago Predeterminado (días)" label="Plazo de Pago Predeterminado (días)"
@@ -63,12 +62,12 @@ const SupplierSettingsCard: React.FC<SupplierSettingsCardProps> = ({
</div> </div>
{/* Performance Thresholds - Delivery */} {/* Performance Thresholds - Delivery */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<TrendingUp className="w-4 h-4 mr-2" /> <TrendingUp className="w-4 h-4 mr-2" />
Umbrales de Rendimiento - Entregas Umbrales de Rendimiento - Entregas
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Tasa de Entrega Excelente (%)" label="Tasa de Entrega Excelente (%)"
@@ -96,12 +95,12 @@ const SupplierSettingsCard: React.FC<SupplierSettingsCardProps> = ({
</div> </div>
{/* Performance Thresholds - Quality */} {/* Performance Thresholds - Quality */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<TrendingUp className="w-4 h-4 mr-2" /> <TrendingUp className="w-4 h-4 mr-2" />
Umbrales de Rendimiento - Calidad Umbrales de Rendimiento - Calidad
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Tasa de Calidad Excelente (%)" label="Tasa de Calidad Excelente (%)"
@@ -129,12 +128,12 @@ const SupplierSettingsCard: React.FC<SupplierSettingsCardProps> = ({
</div> </div>
{/* Critical Alerts */} {/* Critical Alerts */}
<div> <div className="p-4 sm:p-6">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center"> <h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-4 flex items-center">
<AlertTriangle className="w-4 h-4 mr-2" /> <AlertTriangle className="w-4 h-4 mr-2" />
Alertas Críticas Alertas Críticas
</h4> </h4>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 pl-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
<Input <Input
type="number" type="number"
label="Retraso de Entrega Crítico (horas)" label="Retraso de Entrega Crítico (horas)"
@@ -174,14 +173,14 @@ const SupplierSettingsCard: React.FC<SupplierSettingsCardProps> = ({
</div> </div>
{/* Info Box */} {/* Info Box */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4"> <div className="p-4 sm:p-6 bg-blue-50 dark:bg-blue-900/20">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<TrendingUp className="w-5 h-5 text-blue-600 mt-0.5" /> <Info className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
<div className="flex-1"> <div className="flex-1">
<h5 className="text-sm font-semibold text-blue-900 mb-1"> <h5 className="text-sm font-semibold text-blue-900 dark:text-blue-100 mb-1">
Evaluación de Proveedores Evaluación de Proveedores
</h5> </h5>
<p className="text-xs text-blue-700"> <p className="text-xs text-blue-700 dark:text-blue-300">
Estos umbrales se utilizan para evaluar automáticamente el rendimiento de los proveedores. Estos umbrales se utilizan para evaluar automáticamente el rendimiento de los proveedores.
Los proveedores con rendimiento por debajo de los umbrales "buenos" recibirán alertas automáticas. Los proveedores con rendimiento por debajo de los umbrales "buenos" recibirán alertas automáticas.
</p> </p>
@@ -189,7 +188,7 @@ const SupplierSettingsCard: React.FC<SupplierSettingsCardProps> = ({
</div> </div>
</div> </div>
</div> </div>
</Card> </SettingSection>
); );
}; };

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Store, MapPin, Clock, Settings as SettingsIcon, Save, X, AlertCircle, Loader, Bell } from 'lucide-react'; import { Store, MapPin, Clock, Settings as SettingsIcon, Save, X, AlertCircle, Loader, Bell, Globe, Mail, Phone, Building2, CreditCard } from 'lucide-react';
import { Button, Card, Input, Select } from '../../../../components/ui'; import { Button, Card, Input, Select, SettingSection, SettingRow, SettingsSearch } from '../../../../components/ui';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../components/ui/Tabs'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../components/ui/Tabs';
import { PageHeader } from '../../../../components/layout'; import { PageHeader } from '../../../../components/layout';
import { showToast } from '../../../../utils/toast'; import { showToast } from '../../../../utils/toast';
@@ -66,6 +66,7 @@ const BakerySettingsPage: React.FC = () => {
const [activeTab, setActiveTab] = useState('information'); const [activeTab, setActiveTab] = useState('information');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [config, setConfig] = useState<BakeryConfig>({ const [config, setConfig] = useState<BakeryConfig>({
name: '', name: '',
@@ -359,18 +360,24 @@ const BakerySettingsPage: React.FC = () => {
{/* Bakery Header Card */} {/* Bakery Header Card */}
<Card className="p-4 sm:p-6"> <Card className="p-4 sm:p-6">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6"> <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:gap-6">
<div className="w-16 h-16 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] rounded-lg flex items-center justify-center text-white font-bold text-xl flex-shrink-0"> <div className="w-16 h-16 bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] rounded-lg flex items-center justify-center text-white font-bold text-2xl flex-shrink-0 shadow-lg">
{config.name.charAt(0) || 'B'} {config.name.charAt(0) || 'B'}
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<h1 className="text-xl sm:text-2xl font-bold text-text-primary mb-1 truncate"> <h1 className="text-xl sm:text-2xl font-bold text-text-primary mb-1 truncate">
{config.name || t('bakery.information.fields.name')} {config.name || t('bakery.information.fields.name')}
</h1> </h1>
<p className="text-sm sm:text-base text-text-secondary truncate">{config.email}</p> <p className="text-sm sm:text-base text-text-secondary truncate flex items-center gap-2">
<p className="text-xs sm:text-sm text-text-tertiary truncate">{config.address}, {config.city}</p> <Mail className="w-4 h-4" />
{config.email}
</p>
<p className="text-xs sm:text-sm text-text-tertiary truncate flex items-center gap-2 mt-1">
<MapPin className="w-4 h-4" />
{config.address}, {config.city}
</p>
</div> </div>
{hasUnsavedChanges && ( {hasUnsavedChanges && (
<div className="flex items-center gap-2 text-sm text-yellow-600 w-full sm:w-auto"> <div className="flex items-center gap-2 text-sm text-yellow-600 dark:text-yellow-500 bg-yellow-50 dark:bg-yellow-900/20 px-3 py-2 rounded-lg w-full sm:w-auto">
<AlertCircle className="w-4 h-4" /> <AlertCircle className="w-4 h-4" />
<span className="hidden sm:inline">{t('bakery.unsaved_changes')}</span> <span className="hidden sm:inline">{t('bakery.unsaved_changes')}</span>
</div> </div>
@@ -378,6 +385,13 @@ const BakerySettingsPage: React.FC = () => {
</div> </div>
</Card> </Card>
{/* Search Bar */}
<SettingsSearch
placeholder={t('common.search') || 'Search settings...'}
onSearch={setSearchQuery}
className="max-w-md"
/>
{/* Tabs Navigation */} {/* Tabs Navigation */}
<Tabs value={activeTab} onValueChange={setActiveTab}> <Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="w-full sm:w-auto overflow-x-auto"> <TabsList className="w-full sm:w-auto overflow-x-auto">
@@ -403,230 +417,222 @@ const BakerySettingsPage: React.FC = () => {
<TabsContent value="information"> <TabsContent value="information">
<div className="space-y-6"> <div className="space-y-6">
{/* General Information */} {/* General Information */}
<Card className="p-4 sm:p-6"> <SettingSection
<h3 className="text-base sm:text-lg font-semibold text-text-primary mb-4 sm:mb-6 flex items-center"> title={t('bakery.information.general_section')}
<Store className="w-5 h-5 mr-2" /> description="Basic information about your bakery"
{t('bakery.information.general_section')} icon={<Store className="w-5 h-5" />}
</h3> >
<div className="p-4 sm:p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input
label={t('bakery.information.fields.name')}
value={config.name}
onChange={handleInputChange('name')}
error={errors.name}
disabled={isLoading}
placeholder={t('bakery.information.placeholders.name')}
leftIcon={<Store className="w-4 h-4" />}
required
/>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6"> <Input
<Input type="email"
label={t('bakery.information.fields.name')} label={t('bakery.information.fields.email')}
value={config.name} value={config.email}
onChange={handleInputChange('name')} onChange={handleInputChange('email')}
error={errors.name} error={errors.email}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.name')} placeholder={t('bakery.information.placeholders.email')}
leftIcon={<Store className="w-4 h-4" />} leftIcon={<Mail className="w-4 h-4" />}
/> required
/>
<Input <Input
type="email" type="tel"
label={t('bakery.information.fields.email')} label={t('bakery.information.fields.phone')}
value={config.email} value={config.phone}
onChange={handleInputChange('email')} onChange={handleInputChange('phone')}
error={errors.email} disabled={isLoading}
disabled={isLoading} placeholder={t('bakery.information.placeholders.phone')}
placeholder={t('bakery.information.placeholders.email')} leftIcon={<Phone className="w-4 h-4" />}
leftIcon={<MapPin className="w-4 h-4" />} />
/>
<Input <Input
type="tel" label={t('bakery.information.fields.website')}
label={t('bakery.information.fields.phone')} value={config.website}
value={config.phone} onChange={handleInputChange('website')}
onChange={handleInputChange('phone')} disabled={isLoading}
disabled={isLoading} placeholder={t('bakery.information.placeholders.website')}
placeholder={t('bakery.information.placeholders.phone')} leftIcon={<Globe className="w-4 h-4" />}
leftIcon={<Clock className="w-4 h-4" />} />
/> </div>
<Input <div className="mt-4 sm:mt-6">
label={t('bakery.information.fields.website')} <label className="block text-sm font-medium text-text-secondary mb-2">
value={config.website} {t('bakery.information.fields.description')}
onChange={handleInputChange('website')} </label>
disabled={isLoading} <textarea
placeholder={t('bakery.information.placeholders.website')} value={config.description}
className="md:col-span-2 xl:col-span-3" onChange={handleInputChange('description')}
/> disabled={isLoading}
rows={3}
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg resize-none bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)] text-sm sm:text-base focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent"
placeholder={t('bakery.information.placeholders.description')}
/>
</div>
</div> </div>
</SettingSection>
<div className="mt-4 sm:mt-6">
<label className="block text-sm font-medium text-text-secondary mb-2">
{t('bakery.information.fields.description')}
</label>
<textarea
value={config.description}
onChange={handleInputChange('description')}
disabled={isLoading}
rows={3}
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg resize-none bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)] text-sm sm:text-base"
placeholder={t('bakery.information.placeholders.description')}
/>
</div>
</Card>
{/* Location Information */} {/* Location Information */}
<Card className="p-4 sm:p-6"> <SettingSection
<h3 className="text-base sm:text-lg font-semibold text-text-primary mb-4 sm:mb-6 flex items-center"> title={t('bakery.information.location_section')}
<MapPin className="w-5 h-5 mr-2" /> description="Where your bakery is located"
{t('bakery.information.location_section')} icon={<MapPin className="w-5 h-5" />}
</h3> >
<div className="p-4 sm:p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input
label={t('bakery.information.fields.address')}
value={config.address}
onChange={handleInputChange('address')}
error={errors.address}
disabled={isLoading}
placeholder={t('bakery.information.placeholders.address')}
leftIcon={<MapPin className="w-4 h-4" />}
className="md:col-span-2"
required
/>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6"> <Input
<Input label={t('bakery.information.fields.city')}
label={t('bakery.information.fields.address')} value={config.city}
value={config.address} onChange={handleInputChange('city')}
onChange={handleInputChange('address')} error={errors.city}
error={errors.address} disabled={isLoading}
disabled={isLoading} placeholder={t('bakery.information.placeholders.city')}
placeholder={t('bakery.information.placeholders.address')} required
leftIcon={<MapPin className="w-4 h-4" />} />
className="md:col-span-2"
/>
<Input <Input
label={t('bakery.information.fields.city')} label={t('bakery.information.fields.postal_code')}
value={config.city} value={config.postalCode}
onChange={handleInputChange('city')} onChange={handleInputChange('postalCode')}
error={errors.city} disabled={isLoading}
disabled={isLoading} placeholder={t('bakery.information.placeholders.postal_code')}
placeholder={t('bakery.information.placeholders.city')} />
/>
<Input <Input
label={t('bakery.information.fields.postal_code')} label={t('bakery.information.fields.country')}
value={config.postalCode} value={config.country}
onChange={handleInputChange('postalCode')} onChange={handleInputChange('country')}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.postal_code')} placeholder={t('bakery.information.placeholders.country')}
/> />
</div>
<Input
label={t('bakery.information.fields.country')}
value={config.country}
onChange={handleInputChange('country')}
disabled={isLoading}
placeholder={t('bakery.information.placeholders.country')}
/>
</div> </div>
</Card> </SettingSection>
{/* Business Information */} {/* Business Information */}
<Card className="p-4 sm:p-6"> <SettingSection
<h3 className="text-base sm:text-lg font-semibold text-text-primary mb-4 sm:mb-6"> title={t('bakery.information.business_section')}
{t('bakery.information.business_section')} description="Tax and business configuration"
</h3> icon={<Building2 className="w-5 h-5" />}
>
<div className="p-4 sm:p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input
label={t('bakery.information.fields.tax_id')}
value={config.taxId}
onChange={handleInputChange('taxId')}
disabled={isLoading}
placeholder={t('bakery.information.placeholders.tax_id')}
leftIcon={<CreditCard className="w-4 h-4" />}
/>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6"> <Select
<Input label={t('bakery.information.fields.currency')}
label={t('bakery.information.fields.tax_id')} options={currencyOptions}
value={config.taxId} value={config.currency}
onChange={handleInputChange('taxId')} onChange={(value) => handleSelectChange('currency')(value as string)}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.tax_id')} />
/>
<Select <Select
label={t('bakery.information.fields.currency')} label={t('bakery.information.fields.timezone')}
options={currencyOptions} options={timezoneOptions}
value={config.currency} value={config.timezone}
onChange={(value) => handleSelectChange('currency')(value as string)} onChange={(value) => handleSelectChange('timezone')(value as string)}
disabled={isLoading} disabled={isLoading}
/> />
<Select <Select
label={t('bakery.information.fields.timezone')} label={t('bakery.information.fields.language')}
options={timezoneOptions} options={languageOptions}
value={config.timezone} value={config.language}
onChange={(value) => handleSelectChange('timezone')(value as string)} onChange={(value) => handleSelectChange('language')(value as string)}
disabled={isLoading} disabled={isLoading}
/> />
</div>
<Select
label={t('bakery.information.fields.language')}
options={languageOptions}
value={config.language}
onChange={(value) => handleSelectChange('language')(value as string)}
disabled={isLoading}
/>
</div> </div>
</Card> </SettingSection>
</div> </div>
</TabsContent> </TabsContent>
{/* Tab 2: Business Hours */} {/* Tab 2: Business Hours */}
<TabsContent value="hours"> <TabsContent value="hours">
<Card className="p-4 sm:p-6"> <SettingSection
<h3 className="text-base sm:text-lg font-semibold text-text-primary mb-4 sm:mb-6 flex items-center"> title={t('bakery.hours.title')}
<Clock className="w-5 h-5 mr-2" /> description="Configure your opening hours for each day of the week"
{t('bakery.hours.title')} icon={<Clock className="w-5 h-5" />}
</h3> >
{daysOfWeek.map((day) => {
const hours = businessHours[day.key];
return (
<SettingRow
key={day.key}
label={day.label}
description={hours.closed ? t('bakery.hours.closed_all_day') : `${hours.open} - ${hours.close}`}
type="custom"
disabled={isLoading}
>
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 w-full sm:w-auto">
<label className="flex items-center gap-2 whitespace-nowrap">
<input
type="checkbox"
checked={hours.closed}
onChange={(e) => handleHoursChange(day.key, 'closed', e.target.checked)}
disabled={isLoading}
className="rounded border-border-primary"
/>
<span className="text-sm text-text-secondary">{t('bakery.hours.closed')}</span>
</label>
<div className="space-y-3 sm:space-y-4"> {!hours.closed && (
{daysOfWeek.map((day) => { <>
const hours = businessHours[day.key]; <div className="flex items-center gap-2">
return ( <input
<div key={day.key} className="flex flex-col sm:grid sm:grid-cols-12 gap-3 sm:gap-4 p-3 sm:p-4 border border-border-primary rounded-lg"> type="time"
{/* Day Name */} value={hours.open}
<div className="sm:col-span-2"> onChange={(e) => handleHoursChange(day.key, 'open', e.target.value)}
<span className="text-sm font-medium text-text-secondary">{day.label}</span> disabled={isLoading}
</div> className="px-3 py-2 border border-[var(--border-primary)] rounded-lg text-sm bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)]"
/>
{/* Closed Checkbox */} <span className="text-sm text-text-tertiary">-</span>
<div className="sm:col-span-2"> <input
<label className="flex items-center gap-2"> type="time"
<input value={hours.close}
type="checkbox" onChange={(e) => handleHoursChange(day.key, 'close', e.target.value)}
checked={hours.closed} disabled={isLoading}
onChange={(e) => handleHoursChange(day.key, 'closed', e.target.checked)} className="px-3 py-2 border border-[var(--border-primary)] rounded-lg text-sm bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)]"
disabled={isLoading} />
className="rounded border-border-primary"
/>
<span className="text-sm text-text-secondary">{t('bakery.hours.closed')}</span>
</label>
</div>
{/* Time Inputs */}
<div className="sm:col-span-8 flex items-center gap-4 sm:gap-6">
{!hours.closed ? (
<>
<div className="flex-1">
<label className="block text-xs text-text-tertiary mb-1">
{t('bakery.hours.open_time')}
</label>
<input
type="time"
value={hours.open}
onChange={(e) => handleHoursChange(day.key, 'open', e.target.value)}
disabled={isLoading}
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg text-sm bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)]"
/>
</div>
<div className="flex-1">
<label className="block text-xs text-text-tertiary mb-1">
{t('bakery.hours.close_time')}
</label>
<input
type="time"
value={hours.close}
onChange={(e) => handleHoursChange(day.key, 'close', e.target.value)}
disabled={isLoading}
className="w-full px-3 py-2 border border-[var(--border-primary)] rounded-lg text-sm bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:bg-[var(--bg-secondary)] disabled:text-[var(--text-secondary)]"
/>
</div>
</>
) : (
<div className="text-sm text-text-tertiary italic">
{t('bakery.hours.closed_all_day')}
</div> </div>
)} </>
</div> )}
</div> </div>
); </SettingRow>
})} );
</div> })}
</Card> </SettingSection>
</TabsContent> </TabsContent>
{/* Tab 3: Operational Settings */} {/* Tab 3: Operational Settings */}
@@ -720,7 +726,7 @@ const BakerySettingsPage: React.FC = () => {
{/* Floating Save Button */} {/* Floating Save Button */}
{hasUnsavedChanges && ( {hasUnsavedChanges && (
<div className="fixed bottom-6 right-4 sm:right-6 left-4 sm:left-auto z-50"> <div className="fixed bottom-6 right-4 sm:right-6 left-4 sm:left-auto z-50">
<Card className="p-3 sm:p-4 shadow-lg border-2 border-[var(--color-primary)]"> <Card className="p-3 sm:p-4 shadow-xl border-2 border-[var(--color-primary)]">
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3"> <div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
<div className="flex items-center gap-2 text-sm text-text-secondary"> <div className="flex items-center gap-2 text-sm text-text-secondary">
<AlertCircle className="w-4 h-4 text-yellow-500 flex-shrink-0" /> <AlertCircle className="w-4 h-4 text-yellow-500 flex-shrink-0" />

View File

@@ -17,9 +17,10 @@ import {
Trash2, Trash2,
AlertCircle, AlertCircle,
Cookie, Cookie,
ExternalLink ExternalLink,
Check
} from 'lucide-react'; } from 'lucide-react';
import { Button, Card, Avatar, Input, Select } from '../../../../components/ui'; import { Button, Card, Avatar, Input, Select, SettingSection, SettingRow, SettingsSearch } from '../../../../components/ui';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../components/ui/Tabs'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../components/ui/Tabs';
import { PageHeader } from '../../../../components/layout'; import { PageHeader } from '../../../../components/layout';
import { showToast } from '../../../../utils/toast'; import { showToast } from '../../../../utils/toast';
@@ -62,6 +63,7 @@ const NewProfileSettingsPage: React.FC = () => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [showPasswordForm, setShowPasswordForm] = useState(false); const [showPasswordForm, setShowPasswordForm] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
// Export & Delete states // Export & Delete states
const [isExporting, setIsExporting] = useState(false); const [isExporting, setIsExporting] = useState(false);
@@ -103,9 +105,6 @@ const NewProfileSettingsPage: React.FC = () => {
} }
}, [profile]); }, [profile]);
// Subscription status is not needed on the profile page
// It's already shown in the subscription tab of the main ProfilePage
const languageOptions = [ const languageOptions = [
{ value: 'es', label: 'Español' }, { value: 'es', label: 'Español' },
{ value: 'eu', label: 'Euskara' }, { value: 'eu', label: 'Euskara' },
@@ -324,20 +323,31 @@ const NewProfileSettingsPage: React.FC = () => {
<h1 className="text-xl sm:text-2xl font-bold text-text-primary mb-1 truncate"> <h1 className="text-xl sm:text-2xl font-bold text-text-primary mb-1 truncate">
{profileData.first_name} {profileData.last_name} {profileData.first_name} {profileData.last_name}
</h1> </h1>
<p className="text-sm sm:text-base text-text-secondary truncate">{profileData.email}</p> <p className="text-sm sm:text-base text-text-secondary truncate flex items-center gap-2">
<Mail className="w-4 h-4" />
{profileData.email}
</p>
{user?.role && ( {user?.role && (
<p className="text-xs sm:text-sm text-text-tertiary mt-1"> <p className="text-xs sm:text-sm text-text-tertiary mt-1 flex items-center gap-2">
<User className="w-4 h-4" />
{user.role} {user.role}
</p> </p>
)} )}
<div className="flex items-center gap-2 mt-2"> <div className="flex items-center gap-2 mt-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div> <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></div>
<span className="text-xs sm:text-sm text-text-tertiary">{t('profile.online')}</span> <span className="text-xs sm:text-sm text-text-tertiary">{t('profile.online')}</span>
</div> </div>
</div> </div>
</div> </div>
</Card> </Card>
{/* Search Bar */}
<SettingsSearch
placeholder={t('common.search') || 'Search settings...'}
onSearch={setSearchQuery}
className="max-w-md"
/>
{/* Tabs Navigation */} {/* Tabs Navigation */}
<Tabs value={activeTab} onValueChange={setActiveTab}> <Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="w-full sm:w-auto overflow-x-auto"> <TabsList className="w-full sm:w-auto overflow-x-auto">
@@ -359,171 +369,187 @@ const NewProfileSettingsPage: React.FC = () => {
<TabsContent value="personal"> <TabsContent value="personal">
<div className="space-y-6"> <div className="space-y-6">
{/* Profile Form */} {/* Profile Form */}
<Card className="p-4 sm:p-6"> <SettingSection
<h2 className="text-base sm:text-lg font-semibold mb-4">{t('profile.personal_info')}</h2> title={t('profile.personal_info')}
description="Your personal information and account details"
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6"> icon={<User className="w-5 h-5" />}
<Input headerAction={
label={t('profile.fields.first_name')} !isEditing ? (
value={profileData.first_name}
onChange={handleInputChange('first_name')}
error={errors.first_name}
disabled={!isEditing || isLoading}
leftIcon={<User className="w-4 h-4" />}
/>
<Input
label={t('profile.fields.last_name')}
value={profileData.last_name}
onChange={handleInputChange('last_name')}
error={errors.last_name}
disabled={!isEditing || isLoading}
/>
<Input
type="email"
label={t('profile.fields.email')}
value={profileData.email}
onChange={handleInputChange('email')}
error={errors.email}
disabled={!isEditing || isLoading}
leftIcon={<Mail className="w-4 h-4" />}
/>
<Input
type="tel"
label={t('profile.fields.phone')}
value={profileData.phone}
onChange={handleInputChange('phone')}
error={errors.phone}
disabled={!isEditing || isLoading}
placeholder="+34 600 000 000"
leftIcon={<Phone className="w-4 h-4" />}
/>
<Select
label={t('profile.fields.language')}
options={languageOptions}
value={profileData.language}
onChange={handleSelectChange('language')}
disabled={!isEditing || isLoading}
leftIcon={<Globe className="w-4 h-4" />}
/>
<Select
label={t('profile.fields.timezone')}
options={timezoneOptions}
value={profileData.timezone}
onChange={handleSelectChange('timezone')}
disabled={!isEditing || isLoading}
leftIcon={<Clock className="w-4 h-4" />}
/>
</div>
<div className="flex gap-3 mt-6 pt-4 border-t flex-wrap">
{!isEditing ? (
<Button <Button
variant="outline" variant="outline"
size="sm"
onClick={() => setIsEditing(true)} onClick={() => setIsEditing(true)}
className="flex items-center gap-2"
> >
<User className="w-4 h-4" /> <User className="w-4 h-4 mr-2" />
{t('profile.edit_profile')} {t('profile.edit_profile')}
</Button> </Button>
) : ( ) : (
<> <div className="flex gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm"
onClick={() => setIsEditing(false)} onClick={() => setIsEditing(false)}
disabled={isLoading} disabled={isLoading}
className="flex items-center gap-2"
> >
<X className="w-4 h-4" /> <X className="w-4 h-4 mr-1" />
{t('profile.cancel')} {t('profile.cancel')}
</Button> </Button>
<Button <Button
variant="primary" variant="primary"
size="sm"
onClick={handleSaveProfile} onClick={handleSaveProfile}
isLoading={isLoading} isLoading={isLoading}
loadingText={t('common.saving')} loadingText={t('common.saving')}
className="flex items-center gap-2"
> >
<Save className="w-4 h-4" /> <Save className="w-4 h-4 mr-1" />
{t('profile.save_changes')} {t('profile.save_changes')}
</Button> </Button>
</> </div>
)} )
}
>
<div className="p-4 sm:p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input
label={t('profile.fields.first_name')}
value={profileData.first_name}
onChange={handleInputChange('first_name')}
error={errors.first_name}
disabled={!isEditing || isLoading}
leftIcon={<User className="w-4 h-4" />}
required
/>
<Input
label={t('profile.fields.last_name')}
value={profileData.last_name}
onChange={handleInputChange('last_name')}
error={errors.last_name}
disabled={!isEditing || isLoading}
required
/>
<Input
type="email"
label={t('profile.fields.email')}
value={profileData.email}
onChange={handleInputChange('email')}
error={errors.email}
disabled={!isEditing || isLoading}
leftIcon={<Mail className="w-4 h-4" />}
required
/>
<Input
type="tel"
label={t('profile.fields.phone')}
value={profileData.phone}
onChange={handleInputChange('phone')}
error={errors.phone}
disabled={!isEditing || isLoading}
placeholder="+34 600 000 000"
leftIcon={<Phone className="w-4 h-4" />}
/>
<Select
label={t('profile.fields.language')}
options={languageOptions}
value={profileData.language}
onChange={handleSelectChange('language')}
disabled={!isEditing || isLoading}
leftIcon={<Globe className="w-4 h-4" />}
/>
<Select
label={t('profile.fields.timezone')}
options={timezoneOptions}
value={profileData.timezone}
onChange={handleSelectChange('timezone')}
disabled={!isEditing || isLoading}
leftIcon={<Clock className="w-4 h-4" />}
/>
</div>
</div>
</SettingSection>
{/* Security Section */}
<SettingSection
title="Security"
description="Manage your password and security settings"
icon={<Lock className="w-5 h-5" />}
headerAction={
<Button <Button
variant="outline" variant="outline"
size="sm"
onClick={() => setShowPasswordForm(!showPasswordForm)} onClick={() => setShowPasswordForm(!showPasswordForm)}
className="flex items-center gap-2"
> >
<Lock className="w-4 h-4" /> <Lock className="w-4 h-4 mr-2" />
{t('profile.change_password')} {t('profile.change_password')}
</Button> </Button>
</div> }
</Card> >
{showPasswordForm && (
<div className="p-4 sm:p-6">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6 mb-6">
<Input
type="password"
label={t('profile.password.current_password')}
value={passwordData.currentPassword}
onChange={handlePasswordChange('currentPassword')}
error={errors.currentPassword}
disabled={isLoading}
leftIcon={<Lock className="w-4 h-4" />}
required
/>
{/* Password Change Form */} <Input
{showPasswordForm && ( type="password"
<Card className="p-4 sm:p-6"> label={t('profile.password.new_password')}
<h2 className="text-base sm:text-lg font-semibold mb-4">{t('profile.password.title')}</h2> value={passwordData.newPassword}
onChange={handlePasswordChange('newPassword')}
error={errors.newPassword}
disabled={isLoading}
leftIcon={<Lock className="w-4 h-4" />}
required
/>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6 max-w-4xl"> <Input
<Input type="password"
type="password" label={t('profile.password.confirm_password')}
label={t('profile.password.current_password')} value={passwordData.confirmPassword}
value={passwordData.currentPassword} onChange={handlePasswordChange('confirmPassword')}
onChange={handlePasswordChange('currentPassword')} error={errors.confirmPassword}
error={errors.currentPassword} disabled={isLoading}
disabled={isLoading} leftIcon={<Lock className="w-4 h-4" />}
leftIcon={<Lock className="w-4 h-4" />} required
/> />
</div>
<Input <div className="flex gap-3 pt-4 border-t border-[var(--border-primary)] flex-wrap">
type="password" <Button
label={t('profile.password.new_password')} variant="outline"
value={passwordData.newPassword} onClick={() => {
onChange={handlePasswordChange('newPassword')} setShowPasswordForm(false);
error={errors.newPassword} setPasswordData({ currentPassword: '', newPassword: '', confirmPassword: '' });
disabled={isLoading} setErrors({});
leftIcon={<Lock className="w-4 h-4" />} }}
/> disabled={isLoading}
>
<Input {t('profile.cancel')}
type="password" </Button>
label={t('profile.password.confirm_password')} <Button
value={passwordData.confirmPassword} variant="primary"
onChange={handlePasswordChange('confirmPassword')} onClick={handleChangePasswordSubmit}
error={errors.confirmPassword} isLoading={isLoading}
disabled={isLoading} loadingText={t('common.saving')}
leftIcon={<Lock className="w-4 h-4" />} >
/> <Check className="w-4 h-4 mr-2" />
{t('profile.password.change_password')}
</Button>
</div>
</div> </div>
)}
<div className="flex gap-3 pt-6 mt-6 border-t flex-wrap"> </SettingSection>
<Button
variant="outline"
onClick={() => {
setShowPasswordForm(false);
setPasswordData({ currentPassword: '', newPassword: '', confirmPassword: '' });
setErrors({});
}}
disabled={isLoading}
>
{t('profile.cancel')}
</Button>
<Button
variant="primary"
onClick={handleChangePasswordSubmit}
isLoading={isLoading}
loadingText={t('common.saving')}
>
{t('profile.password.change_password')}
</Button>
</div>
</Card>
)}
</div> </div>
</TabsContent> </TabsContent>
@@ -580,84 +606,75 @@ const NewProfileSettingsPage: React.FC = () => {
</Card> </Card>
{/* Cookie Preferences */} {/* Cookie Preferences */}
<Card className="p-4 sm:p-6"> <SettingSection
<div className="flex flex-col sm:flex-row items-start justify-between gap-4"> title={t('profile.privacy.cookie_preferences')}
<div className="flex items-start gap-3 flex-1"> description="Gestiona tus preferencias de cookies"
<Cookie className="w-5 h-5 text-amber-600 mt-1 flex-shrink-0" /> icon={<Cookie className="w-5 h-5" />}
<div> headerAction={
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">
{t('profile.privacy.cookie_preferences')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
Gestiona tus preferencias de cookies
</p>
</div>
</div>
<Button <Button
onClick={() => navigate('/cookie-preferences')} onClick={() => navigate('/cookie-preferences')}
variant="outline" variant="outline"
size="sm" size="sm"
className="w-full sm:w-auto"
> >
<Cookie className="w-4 h-4 mr-2" /> <Cookie className="w-4 h-4 mr-2" />
Gestionar Gestionar
</Button> </Button>
</div> }
</Card> >
<div></div>
</SettingSection>
{/* Data Export */} {/* Data Export */}
<Card className="p-4 sm:p-6"> <SettingSection
<div className="flex items-start gap-3 mb-4"> title={t('profile.privacy.export_data')}
<Download className="w-5 h-5 text-green-600 mt-1 flex-shrink-0" /> description={t('profile.privacy.export_description')}
<div className="flex-1"> icon={<Download className="w-5 h-5" />}
<h3 className="font-semibold text-gray-900 dark:text-white mb-1"> headerAction={
{t('profile.privacy.export_data')} <Button
</h3> onClick={handleDataExport}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2"> variant="primary"
{t('profile.privacy.export_description')} size="sm"
</p> disabled={isExporting}
</div> >
</div> <Download className="w-4 h-4 mr-2" />
{isExporting ? t('common.loading') : t('profile.privacy.export_button')}
<Button </Button>
onClick={handleDataExport} }
variant="primary" >
size="sm" <div></div>
disabled={isExporting} </SettingSection>
className="w-full sm:w-auto"
>
<Download className="w-4 h-4 mr-2" />
{isExporting ? t('common.loading') : t('profile.privacy.export_button')}
</Button>
</Card>
{/* Account Deletion */} {/* Account Deletion */}
<Card className="p-4 sm:p-6 border-red-200 dark:border-red-800"> <SettingSection
<div className="flex items-start gap-3 mb-4"> title={t('profile.privacy.delete_account')}
<AlertCircle className="w-5 h-5 text-red-600 mt-1 flex-shrink-0" /> description={t('profile.privacy.delete_description')}
<div className="flex-1"> icon={<Trash2 className="w-5 h-5" />}
<h3 className="font-semibold text-gray-900 dark:text-white mb-1"> className="border-red-200 dark:border-red-800"
{t('profile.privacy.delete_account')} >
</h3> <div className="p-4 sm:p-6">
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2"> <div className="flex items-start gap-3 mb-4">
{t('profile.privacy.delete_description')} <AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
</p> <div className="flex-1">
<p className="text-xs text-red-600 font-semibold"> <p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
{t('profile.privacy.delete_warning')} {t('profile.privacy.delete_description')}
</p> </p>
<p className="text-xs text-red-600 font-semibold">
{t('profile.privacy.delete_warning')}
</p>
</div>
</div> </div>
</div>
<Button <Button
onClick={() => setShowDeleteModal(true)} onClick={() => setShowDeleteModal(true)}
variant="outline" variant="outline"
size="sm" size="sm"
className="border-red-300 text-red-600 hover:bg-red-50 dark:border-red-700 dark:text-red-400 dark:hover:bg-red-900/20 w-full sm:w-auto" className="border-red-300 text-red-600 hover:bg-red-50 dark:border-red-700 dark:text-red-400 dark:hover:bg-red-900/20"
> >
<Trash2 className="w-4 h-4 mr-2" /> <Trash2 className="w-4 h-4 mr-2" />
{t('profile.privacy.delete_button')} {t('profile.privacy.delete_button')}
</Button> </Button>
</Card> </div>
</SettingSection>
</div> </div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>