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,34 +124,26 @@ 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">
<input
type="checkbox"
id="temperature_monitoring_enabled"
checked={settings.temperature_monitoring_enabled} checked={settings.temperature_monitoring_enabled}
onChange={handleChange('temperature_monitoring_enabled')} onToggle={handleToggleChange('temperature_monitoring_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="When enabled, system will monitor and alert on temperature deviations"
/> />
<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"
@@ -175,11 +170,11 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
</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"
@@ -206,11 +201,11 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
</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"
@@ -238,9 +233,9 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
</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">
@@ -272,8 +267,7 @@ const InventorySettingsCard: React.FC<InventorySettingsCardProps> = ({
)} )}
</div> </div>
</div> </div>
</div> </SettingSection>
</Card>
); );
}; };

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,37 +44,45 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
onChange({ ...settings, [field]: newChannels }); onChange({ ...settings, [field]: newChannels });
}; };
return ( const apiVersionOptions = [
<Card className="p-6"> { value: 'v18.0', label: 'v18.0' },
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-6 flex items-center"> { value: 'v19.0', label: 'v19.0' },
<Bell className="w-5 h-5 mr-2 text-[var(--color-primary)]" /> { value: 'v20.0', label: 'v20.0' }
{t('notification.title')} ];
</h3>
<div className="space-y-6"> const languageOptions = [
{ value: 'es', label: 'Español' },
{ value: 'eu', label: 'Euskara' },
{ value: 'en', label: 'English' }
];
return (
<SettingSection
title={t('notification.title')}
description="Configure WhatsApp, Email, and notification preferences for your bakery"
icon={<Bell className="w-5 h-5" />}
>
<div className="divide-y divide-[var(--border-primary)]">
{/* 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">
<input
type="checkbox"
id="whatsapp_enabled"
checked={settings.whatsapp_enabled} checked={settings.whatsapp_enabled}
onChange={handleChange('whatsapp_enabled')} onToggle={handleToggleChange('whatsapp_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Configure WhatsApp Business API for notifications"
/> />
<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"> <>
<div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
WhatsApp Business API Configuration
</h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
label={t('notification.whatsapp_phone_number_id')} label={t('notification.whatsapp_phone_number_id')}
value={settings.whatsapp_phone_number_id} value={settings.whatsapp_phone_number_id}
@@ -92,44 +111,27 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
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">
@@ -140,33 +142,30 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
</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">
<input
type="checkbox"
id="email_enabled"
checked={settings.email_enabled} checked={settings.email_enabled}
onChange={handleChange('email_enabled')} onToggle={handleToggleChange('email_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Configure email sender details for notifications"
/> />
<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,34 +192,39 @@ 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">
<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> </div>
{/* PO Notifications */}
<div className="divide-y divide-[var(--border-primary)]">
<SettingRow
label={t('notification.enable_po_notifications')}
description="Receive notifications for purchase order updates"
type="toggle"
checked={settings.enable_po_notifications}
onToggle={handleToggleChange('enable_po_notifications')}
disabled={disabled}
helpText="Get notified when POs are created, approved, or delivered"
/>
{settings.enable_po_notifications && ( {settings.enable_po_notifications && (
<div className="pl-6 flex gap-4"> <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)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input <input
type="checkbox" type="checkbox"
@@ -229,6 +233,7 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<Mail className="w-4 h-4" />
Email Email
</label> </label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
@@ -239,29 +244,33 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled || !settings.whatsapp_enabled} disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<MessageSquare className="w-4 h-4" />
WhatsApp WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label> </label>
</div> </div>
</div>
)} )}
</div> </div>
{/* Inventory 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_inventory_alerts" type="toggle"
checked={settings.enable_inventory_alerts} checked={settings.enable_inventory_alerts}
onChange={handleChange('enable_inventory_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_inventory_alerts" className="text-sm font-medium text-[var(--text-secondary)]">
{t('notification.enable_inventory_alerts')}
</label>
</div>
{settings.enable_inventory_alerts && ( {settings.enable_inventory_alerts && (
<div className="pl-6 flex gap-4"> <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)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input <input
type="checkbox" type="checkbox"
@@ -270,6 +279,7 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<Mail className="w-4 h-4" />
Email Email
</label> </label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
@@ -280,29 +290,33 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled || !settings.whatsapp_enabled} disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<MessageSquare className="w-4 h-4" />
WhatsApp WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label> </label>
</div> </div>
</div>
)} )}
</div> </div>
{/* Production Alerts */} {/* Production 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_production_alerts')}
type="checkbox" description="Receive alerts for production schedule and capacity issues"
id="enable_production_alerts" type="toggle"
checked={settings.enable_production_alerts} checked={settings.enable_production_alerts}
onChange={handleChange('enable_production_alerts')} onToggle={handleToggleChange('enable_production_alerts')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Get notified about production delays, capacity warnings, and quality issues"
/> />
<label htmlFor="enable_production_alerts" className="text-sm font-medium text-[var(--text-secondary)]">
{t('notification.enable_production_alerts')}
</label>
</div>
{settings.enable_production_alerts && ( {settings.enable_production_alerts && (
<div className="pl-6 flex gap-4"> <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)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input <input
type="checkbox" type="checkbox"
@@ -311,6 +325,7 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<Mail className="w-4 h-4" />
Email Email
</label> </label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
@@ -321,29 +336,33 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled || !settings.whatsapp_enabled} disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<MessageSquare className="w-4 h-4" />
WhatsApp WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label> </label>
</div> </div>
</div>
)} )}
</div> </div>
{/* Forecast Alerts */} {/* Forecast 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_forecast_alerts')}
type="checkbox" description="Receive alerts for demand forecast and predictions"
id="enable_forecast_alerts" type="toggle"
checked={settings.enable_forecast_alerts} checked={settings.enable_forecast_alerts}
onChange={handleChange('enable_forecast_alerts')} onToggle={handleToggleChange('enable_forecast_alerts')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Get notified about demand predictions and forecast updates"
/> />
<label htmlFor="enable_forecast_alerts" className="text-sm font-medium text-[var(--text-secondary)]">
{t('notification.enable_forecast_alerts')}
</label>
</div>
{settings.enable_forecast_alerts && ( {settings.enable_forecast_alerts && (
<div className="pl-6 flex gap-4"> <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)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
<input <input
type="checkbox" type="checkbox"
@@ -352,6 +371,7 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<Mail className="w-4 h-4" />
Email Email
</label> </label>
<label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]"> <label className="flex items-center gap-2 text-sm text-[var(--text-tertiary)]">
@@ -362,15 +382,16 @@ const NotificationSettingsCard: React.FC<NotificationSettingsCardProps> = ({
disabled={disabled || !settings.whatsapp_enabled} disabled={disabled || !settings.whatsapp_enabled}
className="rounded border-[var(--border-primary)]" className="rounded border-[var(--border-primary)]"
/> />
<MessageSquare className="w-4 h-4" />
WhatsApp WhatsApp
{!settings.whatsapp_enabled && <span className="text-xs">(disabled)</span>}
</label> </label>
</div> </div>
</div>
)} )}
</div> </div>
</div> </div>
</div> </SettingSection>
</div>
</Card>
); );
}; };

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,22 +23,24 @@ 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 (%)"
@@ -49,50 +51,40 @@ const OrderSettingsCard: React.FC<OrderSettingsCardProps> = ({
max={100} max={100}
step={1} step={1}
placeholder="50.0" placeholder="50.0"
helperText="Porcentaje máximo de descuento permitido en pedidos" helperText="Porcentaje máximo de descuento permitido"
/> />
</div> </div>
</div>
<div className="space-y-3"> <SettingRow
<div className="flex items-center gap-2"> label="Habilitar descuentos en pedidos"
<input description="Allow discounts to be applied to orders"
type="checkbox" icon={<Tag className="w-4 h-4" />}
id="discount_enabled" type="toggle"
checked={settings.discount_enabled} checked={settings.discount_enabled}
onChange={handleChange('discount_enabled')} onToggle={handleToggleChange('discount_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="When enabled, discounts can be applied within the maximum limit"
/> />
<label htmlFor="discount_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar descuentos en pedidos
</label>
</div>
<div className="flex items-center gap-2"> <SettingRow
<input label="Habilitar precios dinámicos"
type="checkbox" description="Automatically adjust prices based on demand and inventory"
id="dynamic_pricing_enabled" icon={<TrendingUp className="w-4 h-4" />}
type="toggle"
checked={settings.dynamic_pricing_enabled} checked={settings.dynamic_pricing_enabled}
onChange={handleChange('dynamic_pricing_enabled')} onToggle={handleToggleChange('dynamic_pricing_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="AI-powered dynamic pricing based on market conditions"
/> />
<label htmlFor="dynamic_pricing_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar precios dinámicos
</label>
</div>
</div>
</div>
</div>
{/* 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)"
@@ -103,38 +95,34 @@ const OrderSettingsCard: React.FC<OrderSettingsCardProps> = ({
max={168} max={168}
step={1} step={1}
placeholder="48" placeholder="48"
helperText="Tiempo predeterminado para la entrega de pedidos" helperText="Tiempo predeterminado para entrega"
/> />
</div> </div>
</div>
<div className="flex items-center gap-2"> <SettingRow
<input label="Habilitar seguimiento de entregas"
type="checkbox" description="Allow customers to track their deliveries in real-time"
id="delivery_tracking_enabled" icon={<MapPin className="w-4 h-4" />}
type="toggle"
checked={settings.delivery_tracking_enabled} checked={settings.delivery_tracking_enabled}
onChange={handleChange('delivery_tracking_enabled')} onToggle={handleToggleChange('delivery_tracking_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Enables real-time delivery tracking for customers"
/> />
<label htmlFor="delivery_tracking_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar seguimiento de entregas
</label>
</div>
</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">
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>
</div>
<div className="space-y-3"> {/* Auto-sync Options */}
<div className="flex items-center gap-2"> <SettingRow
<input label="Sincronización automática de productos"
type="checkbox" description="Automatically sync product changes to POS terminals"
id="auto_sync_products" icon={<RefreshCw className="w-4 h-4" />}
type="toggle"
checked={settings.auto_sync_products} checked={settings.auto_sync_products}
onChange={handleChange('auto_sync_products')} onToggle={handleToggleChange('auto_sync_products')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="When enabled, product updates are immediately pushed to POS"
/> />
<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"> <SettingRow
<input label="Sincronización automática de transacciones"
type="checkbox" description="Automatically sync transactions from POS terminals"
id="auto_sync_transactions" icon={<RefreshCw className="w-4 h-4" />}
type="toggle"
checked={settings.auto_sync_transactions} checked={settings.auto_sync_transactions}
onChange={handleChange('auto_sync_transactions')} onToggle={handleToggleChange('auto_sync_transactions')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="When enabled, sales are automatically synced from POS"
/> />
<label htmlFor="auto_sync_transactions" className="text-sm text-[var(--text-secondary)]">
Sincronización automática de transacciones
</label>
</div>
</div>
</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">
<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,41 +26,43 @@ 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">
<input
type="checkbox"
id="auto_approve_enabled"
checked={settings.auto_approve_enabled} checked={settings.auto_approve_enabled}
onChange={handleChange('auto_approve_enabled')} onToggle={handleToggleChange('auto_approve_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="When enabled, POs below threshold with good suppliers are auto-approved"
/> />
<label htmlFor="auto_approve_enabled" className="text-sm text-[var(--text-secondary)]">
{t('procurement.auto_approve_enabled')}
</label>
</div>
{settings.auto_approve_enabled && (
<>
<div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
Auto-Approval Thresholds
</h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label={t('procurement.auto_approve_threshold')} label={t('procurement.auto_approve_threshold')}
value={settings.auto_approve_threshold_eur} value={settings.auto_approve_threshold_eur}
onChange={handleChange('auto_approve_threshold_eur')} onChange={handleChange('auto_approve_threshold_eur')}
disabled={disabled || !settings.auto_approve_enabled} disabled={disabled}
min={0} min={0}
max={10000} max={10000}
step={50} step={50}
@@ -72,50 +74,43 @@ const ProcurementSettingsCard: React.FC<ProcurementSettingsCardProps> = ({
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">
<h4 className="text-sm font-semibold text-[var(--text-secondary)] mb-2 flex items-center">
<Brain className="w-4 h-4 mr-2" /> <Brain className="w-4 h-4 mr-2" />
{t('procurement.smart_procurement')} {t('procurement.smart_procurement')}
</h4> </h4>
<div className="space-y-3 pl-6"> <p className="text-xs text-[var(--text-tertiary)] mb-4">
<div className="flex items-start gap-2"> Intelligent rules for optimizing procurement decisions
<input </p>
type="checkbox" </div>
id="use_reorder_rules"
<SettingRow
label={t('procurement.use_reorder_rules')}
description={t('procurement.use_reorder_rules_desc')}
type="toggle"
checked={settings.use_reorder_rules} checked={settings.use_reorder_rules}
onChange={handleChange('use_reorder_rules')} onToggle={handleToggleChange('use_reorder_rules')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5" helpText="Automatically calculate when to reorder based on usage patterns"
/> />
<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"> <SettingRow
<input label={t('procurement.economic_rounding')}
type="checkbox" description={t('procurement.economic_rounding_desc')}
id="economic_rounding" type="toggle"
checked={settings.economic_rounding} checked={settings.economic_rounding}
onChange={handleChange('economic_rounding')} onToggle={handleToggleChange('economic_rounding')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5" helpText="Round order quantities to economical batch sizes"
/> />
<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"> <SettingRow
<input label={t('procurement.respect_storage_limits')}
type="checkbox" description={t('procurement.respect_storage_limits_desc')}
id="respect_storage_limits" type="toggle"
checked={settings.respect_storage_limits} checked={settings.respect_storage_limits}
onChange={handleChange('respect_storage_limits')} onToggle={handleToggleChange('respect_storage_limits')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5" helpText="Ensure orders don't exceed available storage capacity"
/> />
<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"> <SettingRow
<input label={t('procurement.use_supplier_minimums')}
type="checkbox" description={t('procurement.use_supplier_minimums_desc')}
id="use_supplier_minimums" type="toggle"
checked={settings.use_supplier_minimums} checked={settings.use_supplier_minimums}
onChange={handleChange('use_supplier_minimums')} onToggle={handleToggleChange('use_supplier_minimums')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5" helpText="Respect minimum order quantities set by suppliers"
/> />
<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"> <SettingRow
<input label={t('procurement.optimize_price_tiers')}
type="checkbox" description={t('procurement.optimize_price_tiers_desc')}
id="optimize_price_tiers" type="toggle"
checked={settings.optimize_price_tiers} checked={settings.optimize_price_tiers}
onChange={handleChange('optimize_price_tiers')} onToggle={handleToggleChange('optimize_price_tiers')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)] mt-0.5" helpText="Automatically optimize order quantities to get best price tiers"
/> />
<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>
</div> </SettingSection>
</div>
</div>
</Card>
); );
}; };

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>
</div>
<div className="flex items-center gap-2 md:col-span-2 xl:col-span-2"> {/* Schedule Optimization */}
<input <div className="divide-y divide-[var(--border-primary)]">
type="checkbox" <SettingRow
id="schedule_optimization_enabled" label="Optimización de Horarios"
description="Enable intelligent schedule optimization for production batches"
type="toggle"
checked={settings.schedule_optimization_enabled} checked={settings.schedule_optimization_enabled}
onChange={handleChange('schedule_optimization_enabled')} onToggle={handleToggleChange('schedule_optimization_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="Uses AI to optimize production schedules based on capacity and demand"
/> />
<label htmlFor="schedule_optimization_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar optimización de horarios
</label>
</div>
</div>
</div> </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,33 +162,30 @@ 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">
<input
type="checkbox"
id="quality_check_enabled"
checked={settings.quality_check_enabled} checked={settings.quality_check_enabled}
onChange={handleChange('quality_check_enabled')} onToggle={handleToggleChange('quality_check_enabled')}
disabled={disabled} disabled={disabled}
className="rounded border-[var(--border-primary)]" helpText="When enabled, production batches require quality verification"
/> />
<label htmlFor="quality_check_enabled" className="text-sm text-[var(--text-secondary)]">
Habilitar verificación de calidad
</label>
</div>
{settings.quality_check_enabled && (
<div className="p-4 sm:p-6 bg-[var(--bg-secondary)]">
<h5 className="text-sm font-medium text-[var(--text-secondary)] mb-4">
Parámetros de Calidad
</h5>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input <Input
type="number" type="number"
label="Rendimiento Mínimo (%)" label="Rendimiento Mínimo (%)"
value={settings.minimum_yield_percentage} value={settings.minimum_yield_percentage}
onChange={handleChange('minimum_yield_percentage')} onChange={handleChange('minimum_yield_percentage')}
disabled={disabled || !settings.quality_check_enabled} disabled={disabled}
min={50} min={50}
max={100} max={100}
step={1} step={1}
@@ -198,7 +197,7 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
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}
@@ -206,15 +205,16 @@ const ProductionSettingsCard: React.FC<ProductionSettingsCardProps> = ({
/> />
</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,13 +417,13 @@ 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 xl:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input <Input
label={t('bakery.information.fields.name')} label={t('bakery.information.fields.name')}
value={config.name} value={config.name}
@@ -418,6 +432,7 @@ const BakerySettingsPage: React.FC = () => {
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.name')} placeholder={t('bakery.information.placeholders.name')}
leftIcon={<Store className="w-4 h-4" />} leftIcon={<Store className="w-4 h-4" />}
required
/> />
<Input <Input
@@ -428,7 +443,8 @@ const BakerySettingsPage: React.FC = () => {
error={errors.email} error={errors.email}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.email')} placeholder={t('bakery.information.placeholders.email')}
leftIcon={<MapPin className="w-4 h-4" />} leftIcon={<Mail className="w-4 h-4" />}
required
/> />
<Input <Input
@@ -438,7 +454,7 @@ const BakerySettingsPage: React.FC = () => {
onChange={handleInputChange('phone')} onChange={handleInputChange('phone')}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.phone')} placeholder={t('bakery.information.placeholders.phone')}
leftIcon={<Clock className="w-4 h-4" />} leftIcon={<Phone className="w-4 h-4" />}
/> />
<Input <Input
@@ -447,7 +463,7 @@ const BakerySettingsPage: React.FC = () => {
onChange={handleInputChange('website')} onChange={handleInputChange('website')}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.website')} placeholder={t('bakery.information.placeholders.website')}
className="md:col-span-2 xl:col-span-3" leftIcon={<Globe className="w-4 h-4" />}
/> />
</div> </div>
@@ -460,20 +476,21 @@ const BakerySettingsPage: React.FC = () => {
onChange={handleInputChange('description')} onChange={handleInputChange('description')}
disabled={isLoading} disabled={isLoading}
rows={3} 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" 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')} placeholder={t('bakery.information.placeholders.description')}
/> />
</div> </div>
</Card> </div>
</SettingSection>
{/* 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 xl:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input <Input
label={t('bakery.information.fields.address')} label={t('bakery.information.fields.address')}
value={config.address} value={config.address}
@@ -483,6 +500,7 @@ const BakerySettingsPage: React.FC = () => {
placeholder={t('bakery.information.placeholders.address')} placeholder={t('bakery.information.placeholders.address')}
leftIcon={<MapPin className="w-4 h-4" />} leftIcon={<MapPin className="w-4 h-4" />}
className="md:col-span-2" className="md:col-span-2"
required
/> />
<Input <Input
@@ -492,6 +510,7 @@ const BakerySettingsPage: React.FC = () => {
error={errors.city} error={errors.city}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.city')} placeholder={t('bakery.information.placeholders.city')}
required
/> />
<Input <Input
@@ -510,21 +529,24 @@ const BakerySettingsPage: React.FC = () => {
placeholder={t('bakery.information.placeholders.country')} placeholder={t('bakery.information.placeholders.country')}
/> />
</div> </div>
</Card> </div>
</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="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6"> <div className="p-4 sm:p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6">
<Input <Input
label={t('bakery.information.fields.tax_id')} label={t('bakery.information.fields.tax_id')}
value={config.taxId} value={config.taxId}
onChange={handleInputChange('taxId')} onChange={handleInputChange('taxId')}
disabled={isLoading} disabled={isLoading}
placeholder={t('bakery.information.placeholders.tax_id')} placeholder={t('bakery.information.placeholders.tax_id')}
leftIcon={<CreditCard className="w-4 h-4" />}
/> />
<Select <Select
@@ -551,31 +573,30 @@ const BakerySettingsPage: React.FC = () => {
disabled={isLoading} disabled={isLoading}
/> />
</div> </div>
</Card> </div>
</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> >
<div className="space-y-3 sm:space-y-4">
{daysOfWeek.map((day) => { {daysOfWeek.map((day) => {
const hours = businessHours[day.key]; const hours = businessHours[day.key];
return ( return (
<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"> <SettingRow
{/* Day Name */} key={day.key}
<div className="sm:col-span-2"> label={day.label}
<span className="text-sm font-medium text-text-secondary">{day.label}</span> description={hours.closed ? t('bakery.hours.closed_all_day') : `${hours.open} - ${hours.close}`}
</div> type="custom"
disabled={isLoading}
{/* Closed Checkbox */} >
<div className="sm:col-span-2"> <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"> <label className="flex items-center gap-2 whitespace-nowrap">
<input <input
type="checkbox" type="checkbox"
checked={hours.closed} checked={hours.closed}
@@ -585,48 +606,33 @@ const BakerySettingsPage: React.FC = () => {
/> />
<span className="text-sm text-text-secondary">{t('bakery.hours.closed')}</span> <span className="text-sm text-text-secondary">{t('bakery.hours.closed')}</span>
</label> </label>
</div>
{/* Time Inputs */} {!hours.closed && (
<div className="sm:col-span-8 flex items-center gap-4 sm:gap-6">
{!hours.closed ? (
<> <>
<div className="flex-1"> <div className="flex items-center gap-2">
<label className="block text-xs text-text-tertiary mb-1">
{t('bakery.hours.open_time')}
</label>
<input <input
type="time" type="time"
value={hours.open} value={hours.open}
onChange={(e) => handleHoursChange(day.key, 'open', e.target.value)} onChange={(e) => handleHoursChange(day.key, 'open', e.target.value)}
disabled={isLoading} 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)]" 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)]"
/> />
</div> <span className="text-sm text-text-tertiary">-</span>
<div className="flex-1">
<label className="block text-xs text-text-tertiary mb-1">
{t('bakery.hours.close_time')}
</label>
<input <input
type="time" type="time"
value={hours.close} value={hours.close}
onChange={(e) => handleHoursChange(day.key, 'close', e.target.value)} onChange={(e) => handleHoursChange(day.key, 'close', e.target.value)}
disabled={isLoading} 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)]" 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)]"
/> />
</div> </div>
</> </>
) : (
<div className="text-sm text-text-tertiary italic">
{t('bakery.hours.closed_all_day')}
</div>
)} )}
</div> </div>
</div> </SettingRow>
); );
})} })}
</div> </SettingSection>
</Card>
</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,10 +369,47 @@ 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" />}
headerAction={
!isEditing ? (
<Button
variant="outline"
size="sm"
onClick={() => setIsEditing(true)}
>
<User className="w-4 h-4 mr-2" />
{t('profile.edit_profile')}
</Button>
) : (
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsEditing(false)}
disabled={isLoading}
>
<X className="w-4 h-4 mr-1" />
{t('profile.cancel')}
</Button>
<Button
variant="primary"
size="sm"
onClick={handleSaveProfile}
isLoading={isLoading}
loadingText={t('common.saving')}
>
<Save className="w-4 h-4 mr-1" />
{t('profile.save_changes')}
</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 <Input
label={t('profile.fields.first_name')} label={t('profile.fields.first_name')}
value={profileData.first_name} value={profileData.first_name}
@@ -370,6 +417,7 @@ const NewProfileSettingsPage: React.FC = () => {
error={errors.first_name} error={errors.first_name}
disabled={!isEditing || isLoading} disabled={!isEditing || isLoading}
leftIcon={<User className="w-4 h-4" />} leftIcon={<User className="w-4 h-4" />}
required
/> />
<Input <Input
@@ -378,6 +426,7 @@ const NewProfileSettingsPage: React.FC = () => {
onChange={handleInputChange('last_name')} onChange={handleInputChange('last_name')}
error={errors.last_name} error={errors.last_name}
disabled={!isEditing || isLoading} disabled={!isEditing || isLoading}
required
/> />
<Input <Input
@@ -388,6 +437,7 @@ const NewProfileSettingsPage: React.FC = () => {
error={errors.email} error={errors.email}
disabled={!isEditing || isLoading} disabled={!isEditing || isLoading}
leftIcon={<Mail className="w-4 h-4" />} leftIcon={<Mail className="w-4 h-4" />}
required
/> />
<Input <Input
@@ -419,57 +469,28 @@ const NewProfileSettingsPage: React.FC = () => {
leftIcon={<Clock className="w-4 h-4" />} leftIcon={<Clock className="w-4 h-4" />}
/> />
</div> </div>
</div>
</SettingSection>
<div className="flex gap-3 mt-6 pt-4 border-t flex-wrap"> {/* Security Section */}
{!isEditing ? ( <SettingSection
<Button title="Security"
variant="outline" description="Manage your password and security settings"
onClick={() => setIsEditing(true)} icon={<Lock className="w-5 h-5" />}
className="flex items-center gap-2" headerAction={
>
<User className="w-4 h-4" />
{t('profile.edit_profile')}
</Button>
) : (
<>
<Button
variant="outline"
onClick={() => setIsEditing(false)}
disabled={isLoading}
className="flex items-center gap-2"
>
<X className="w-4 h-4" />
{t('profile.cancel')}
</Button>
<Button
variant="primary"
onClick={handleSaveProfile}
isLoading={isLoading}
loadingText={t('common.saving')}
className="flex items-center gap-2"
>
<Save className="w-4 h-4" />
{t('profile.save_changes')}
</Button>
</>
)}
<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> >
{/* Password Change Form */}
{showPasswordForm && ( {showPasswordForm && (
<Card className="p-4 sm:p-6"> <div className="p-4 sm:p-6">
<h2 className="text-base sm:text-lg font-semibold mb-4">{t('profile.password.title')}</h2> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4 sm:gap-6 mb-6">
<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.current_password')} label={t('profile.password.current_password')}
@@ -478,6 +499,7 @@ const NewProfileSettingsPage: React.FC = () => {
error={errors.currentPassword} error={errors.currentPassword}
disabled={isLoading} disabled={isLoading}
leftIcon={<Lock className="w-4 h-4" />} leftIcon={<Lock className="w-4 h-4" />}
required
/> />
<Input <Input
@@ -488,6 +510,7 @@ const NewProfileSettingsPage: React.FC = () => {
error={errors.newPassword} error={errors.newPassword}
disabled={isLoading} disabled={isLoading}
leftIcon={<Lock className="w-4 h-4" />} leftIcon={<Lock className="w-4 h-4" />}
required
/> />
<Input <Input
@@ -498,10 +521,11 @@ const NewProfileSettingsPage: React.FC = () => {
error={errors.confirmPassword} error={errors.confirmPassword}
disabled={isLoading} disabled={isLoading}
leftIcon={<Lock className="w-4 h-4" />} leftIcon={<Lock className="w-4 h-4" />}
required
/> />
</div> </div>
<div className="flex gap-3 pt-6 mt-6 border-t flex-wrap"> <div className="flex gap-3 pt-4 border-t border-[var(--border-primary)] flex-wrap">
<Button <Button
variant="outline" variant="outline"
onClick={() => { onClick={() => {
@@ -519,11 +543,13 @@ const NewProfileSettingsPage: React.FC = () => {
isLoading={isLoading} isLoading={isLoading}
loadingText={t('common.saving')} loadingText={t('common.saving')}
> >
<Check className="w-4 h-4 mr-2" />
{t('profile.password.change_password')} {t('profile.password.change_password')}
</Button> </Button>
</div> </div>
</Card> </div>
)} )}
</SettingSection>
</div> </div>
</TabsContent> </TabsContent>
@@ -580,65 +606,55 @@ 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')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
{t('profile.privacy.export_description')}
</p>
</div>
</div>
<Button <Button
onClick={handleDataExport} onClick={handleDataExport}
variant="primary" variant="primary"
size="sm" size="sm"
disabled={isExporting} disabled={isExporting}
className="w-full sm:w-auto"
> >
<Download className="w-4 h-4 mr-2" /> <Download className="w-4 h-4 mr-2" />
{isExporting ? t('common.loading') : t('profile.privacy.export_button')} {isExporting ? t('common.loading') : t('profile.privacy.export_button')}
</Button> </Button>
</Card> }
>
<div></div>
</SettingSection>
{/* Account Deletion */} {/* Account Deletion */}
<Card className="p-4 sm:p-6 border-red-200 dark:border-red-800"> <SettingSection
title={t('profile.privacy.delete_account')}
description={t('profile.privacy.delete_description')}
icon={<Trash2 className="w-5 h-5" />}
className="border-red-200 dark:border-red-800"
>
<div className="p-4 sm:p-6">
<div className="flex items-start gap-3 mb-4"> <div className="flex items-start gap-3 mb-4">
<AlertCircle className="w-5 h-5 text-red-600 mt-1 flex-shrink-0" /> <AlertCircle className="w-5 h-5 text-red-600 mt-0.5 flex-shrink-0" />
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">
{t('profile.privacy.delete_account')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2"> <p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
{t('profile.privacy.delete_description')} {t('profile.privacy.delete_description')}
</p> </p>
@@ -652,12 +668,13 @@ const NewProfileSettingsPage: React.FC = () => {
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>