Improve UI

This commit is contained in:
Urtzi Alfaro
2025-12-30 14:40:20 +01:00
parent e494ea8635
commit c07df124fb
71 changed files with 647 additions and 265 deletions

View File

@@ -8,6 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/Card';
import { Badge } from '../ui/Badge';
import { BarChart3, TrendingUp, TrendingDown, ArrowUp, ArrowDown, ExternalLink, Package, ShoppingCart } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../hooks/useTenantCurrency';
interface PerformanceDataPoint {
rank: number;
@@ -30,6 +31,7 @@ const PerformanceChart: React.FC<PerformanceChartProps> = ({
onOutletClick
}) => {
const { t } = useTranslation('dashboard');
const { currencySymbol } = useTenantCurrency();
// Get metric info
const getMetricInfo = () => {
@@ -38,14 +40,14 @@ const PerformanceChart: React.FC<PerformanceChartProps> = ({
return {
icon: <TrendingUp className="w-4 h-4" />,
label: t('enterprise.metrics.sales'),
unit: '€',
unit: currencySymbol,
format: (val: number) => val.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
};
case 'inventory_value':
return {
icon: <Package className="w-4 h-4" />,
label: t('enterprise.metrics.inventory_value'),
unit: '€',
unit: currencySymbol,
format: (val: number) => val.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
};
case 'order_frequency':

View File

@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
import { useDistributionOverview } from '../../api/hooks/useEnterpriseDashboard';
import { useSSEEvents } from '../../hooks/useSSE';
import StatusCard from '../ui/StatusCard/StatusCard';
import { useTenantCurrency } from '../../hooks/useTenantCurrency';
interface DistributionTabProps {
tenantId: string;
@@ -20,6 +21,7 @@ interface DistributionTabProps {
const DistributionTab: React.FC<DistributionTabProps> = ({ tenantId, selectedDate, onDateChange }) => {
const { t } = useTranslation('dashboard');
const { currencySymbol } = useTenantCurrency();
// Get distribution data
const {
@@ -317,7 +319,7 @@ const DistributionTab: React.FC<DistributionTabProps> = ({ tenantId, selectedDat
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-[var(--color-info)]">
{optimizationMetrics.fuelSaved.toFixed(2)}
{currencySymbol}{optimizationMetrics.fuelSaved.toFixed(2)}
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('enterprise.estimated_fuel_savings')}

View File

@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
import { useChildrenPerformance } from '../../api/hooks/useEnterpriseDashboard';
import PerformanceChart from '../charts/PerformanceChart';
import StatusCard from '../ui/StatusCard/StatusCard';
import { useTenantCurrency } from '../../hooks/useTenantCurrency';
interface NetworkPerformanceTabProps {
tenantId: string;
@@ -19,6 +20,7 @@ interface NetworkPerformanceTabProps {
const NetworkPerformanceTab: React.FC<NetworkPerformanceTabProps> = ({ tenantId, onOutletClick }) => {
const { t } = useTranslation('dashboard');
const { currencySymbol } = useTenantCurrency();
const [selectedMetric, setSelectedMetric] = useState('sales');
const [selectedPeriod, setSelectedPeriod] = useState(30);
const [viewMode, setViewMode] = useState<'chart' | 'cards'>('chart');
@@ -216,8 +218,8 @@ const NetworkPerformanceTab: React.FC<NetworkPerformanceTabProps> = ({ tenantId,
</CardHeader>
<CardContent>
<div className="text-3xl font-bold text-[var(--color-success)]">
{selectedMetric === 'sales' ? `${networkMetrics.avgSales.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${networkMetrics.avgInventory.toLocaleString()}` :
{selectedMetric === 'sales' ? `${currencySymbol}${networkMetrics.avgSales.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${currencySymbol}${networkMetrics.avgInventory.toLocaleString()}` :
networkMetrics.avgOrders.toLocaleString()}
</div>
<p className="text-xs text-[var(--text-secondary)] mt-1">
@@ -266,8 +268,8 @@ const NetworkPerformanceTab: React.FC<NetworkPerformanceTabProps> = ({ tenantId,
}}
title={networkMetrics.topPerformer.outlet_name}
subtitle={t('enterprise.best_in_network')}
primaryValue={selectedMetric === 'sales' ? `${networkMetrics.topPerformer.metric_value.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${networkMetrics.topPerformer.metric_value.toLocaleString()}` :
primaryValue={selectedMetric === 'sales' ? `${currencySymbol}${networkMetrics.topPerformer.metric_value.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${currencySymbol}${networkMetrics.topPerformer.metric_value.toLocaleString()}` :
networkMetrics.topPerformer.metric_value.toLocaleString()}
primaryValueLabel={selectedMetric === 'sales' ? t('enterprise.sales') :
selectedMetric === 'inventory_value' ? t('enterprise.inventory_value') :
@@ -305,8 +307,8 @@ const NetworkPerformanceTab: React.FC<NetworkPerformanceTabProps> = ({ tenantId,
}}
title={networkMetrics.bottomPerformer.outlet_name}
subtitle={t('enterprise.improvement_opportunity')}
primaryValue={selectedMetric === 'sales' ? `${networkMetrics.bottomPerformer.metric_value.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${networkMetrics.bottomPerformer.metric_value.toLocaleString()}` :
primaryValue={selectedMetric === 'sales' ? `${currencySymbol}${networkMetrics.bottomPerformer.metric_value.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${currencySymbol}${networkMetrics.bottomPerformer.metric_value.toLocaleString()}` :
networkMetrics.bottomPerformer.metric_value.toLocaleString()}
primaryValueLabel={selectedMetric === 'sales' ? t('enterprise.sales') :
selectedMetric === 'inventory_value' ? t('enterprise.inventory_value') :
@@ -429,8 +431,8 @@ const NetworkPerformanceTab: React.FC<NetworkPerformanceTabProps> = ({ tenantId,
}}
title={outlet.outlet_name}
subtitle={`#${index + 1} ${t('enterprise.of')} ${childrenPerformance.rankings.length}`}
primaryValue={selectedMetric === 'sales' ? `${outlet.metric_value.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${outlet.metric_value.toLocaleString()}` :
primaryValue={selectedMetric === 'sales' ? `${currencySymbol}${outlet.metric_value.toLocaleString()}` :
selectedMetric === 'inventory_value' ? `${currencySymbol}${outlet.metric_value.toLocaleString()}` :
outlet.metric_value.toLocaleString()}
primaryValueLabel={selectedMetric === 'sales' ? t('enterprise.sales') :
selectedMetric === 'inventory_value' ? t('enterprise.inventory_value') :

View File

@@ -16,6 +16,7 @@ import {
} from 'chart.js';
import { Card, CardContent } from '../ui/Card';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../hooks/useTenantCurrency';
// Register Chart.js components
ChartJS.register(
@@ -42,6 +43,7 @@ interface PerformanceChartProps {
export const PerformanceChart: React.FC<PerformanceChartProps> = ({ data, metric, period }) => {
const { t } = useTranslation('dashboard');
const { currencySymbol } = useTenantCurrency();
// Prepare chart data
const chartData = {
@@ -76,7 +78,7 @@ export const PerformanceChart: React.FC<PerformanceChartProps> = ({ data, metric
}
if (context.parsed.y !== null) {
if (metric === 'sales') {
label += `${context.parsed.y.toFixed(2)}`;
label += `${currencySymbol}${context.parsed.y.toFixed(2)}`;
} else {
label += context.parsed.y;
}
@@ -142,7 +144,7 @@ export const PerformanceChart: React.FC<PerformanceChartProps> = ({ data, metric
<td className="px-3 py-2">{item.rank}</td>
<td className="px-3 py-2 font-medium">{item.anonymized_name}</td>
<td className="px-3 py-2 text-right">
{metric === 'sales' ? `${item.metric_value.toFixed(2)}` : item.metric_value}
{metric === 'sales' ? `${currencySymbol}${item.metric_value.toFixed(2)}` : item.metric_value}
</td>
</tr>
))}

View File

@@ -19,6 +19,7 @@ import {
ShoppingCart,
X,
} from 'lucide-react';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface PendingPurchasesBlockProps {
pendingPOs: any[];
@@ -36,6 +37,7 @@ export function PendingPurchasesBlock({
loading,
}: PendingPurchasesBlockProps) {
const { t } = useTranslation(['dashboard', 'common']);
const { currencySymbol } = useTenantCurrency();
const [expandedReasoningId, setExpandedReasoningId] = useState<string | null>(null);
const [processingId, setProcessingId] = useState<string | null>(null);
@@ -288,7 +290,7 @@ export function PendingPurchasesBlock({
</p>
<p className="text-lg font-bold text-[var(--text-primary)]">
{(po.total_amount || po.total || 0).toLocaleString(undefined, {
{currencySymbol}{(po.total_amount || po.total || 0).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})}

View File

@@ -22,6 +22,7 @@ import {
TrendingUp,
} from 'lucide-react';
import type { ControlPanelData, OrchestrationSummary } from '../../../api/hooks/useControlPanelData';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface SystemStatusBlockProps {
data: ControlPanelData | undefined;
@@ -30,6 +31,7 @@ interface SystemStatusBlockProps {
export function SystemStatusBlock({ data, loading }: SystemStatusBlockProps) {
const { t } = useTranslation(['dashboard', 'common']);
const { currencySymbol } = useTenantCurrency();
const [isExpanded, setIsExpanded] = useState(false);
if (loading) {
@@ -234,7 +236,7 @@ export function SystemStatusBlock({ data, loading }: SystemStatusBlockProps) {
{t('dashboard:new_dashboard.system_status.estimated_savings')}
</div>
<div className="text-xl font-bold text-[var(--color-success-600)]">
{orchestrationSummary.estimatedSavingsEur.toLocaleString()}
{currencySymbol}{orchestrationSummary.estimatedSavingsEur.toLocaleString()}
</div>
</div>
)}
@@ -266,7 +268,7 @@ export function SystemStatusBlock({ data, loading }: SystemStatusBlockProps) {
</p>
{issue.business_impact?.financial_impact_eur && (
<p className="text-xs text-[var(--text-secondary)]">
{t('dashboard:new_dashboard.system_status.saved')}: {issue.business_impact.financial_impact_eur.toLocaleString()}
{t('dashboard:new_dashboard.system_status.saved')}: {currencySymbol}{issue.business_impact.financial_impact_eur.toLocaleString()}
</p>
)}
</div>

View File

@@ -4,10 +4,11 @@ import { ChartWidget } from './ChartWidget';
import { ReportsTable } from './ReportsTable';
import { FilterPanel } from './FilterPanel';
import { ExportOptions } from './ExportOptions';
import type {
BakeryMetrics,
AnalyticsReport,
ChartWidget as ChartWidgetType,
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
import type {
BakeryMetrics,
AnalyticsReport,
ChartWidget as ChartWidgetType,
FilterPanel as FilterPanelType,
AppliedFilter,
TimeRange,
@@ -45,6 +46,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
onMetricsLoad,
onExport,
}) => {
const { currencySymbol } = useTenantCurrency();
const [selectedTimeRange, setSelectedTimeRange] = useState<TimeRange>(initialTimeRange);
const [customDateRange, setCustomDateRange] = useState<{ from: Date; to: Date } | null>(null);
const [bakeryMetrics, setBakeryMetrics] = useState<BakeryMetrics | null>(null);
@@ -319,7 +321,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{renderKPICard(
'Ingresos Totales',
`${bakeryMetrics.sales.total_revenue.toLocaleString()}`,
`${currencySymbol}${bakeryMetrics.sales.total_revenue.toLocaleString()}`,
undefined,
bakeryMetrics.sales.revenue_growth,
'💰',
@@ -328,7 +330,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
{renderKPICard(
'Pedidos',
bakeryMetrics.sales.total_orders.toLocaleString(),
`Ticket medio: ${bakeryMetrics.sales.average_order_value.toFixed(2)}`,
`Ticket medio: ${currencySymbol}${bakeryMetrics.sales.average_order_value.toFixed(2)}`,
bakeryMetrics.sales.order_growth,
'📦',
'text-[var(--color-info)]'
@@ -336,7 +338,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
{renderKPICard(
'Margen de Beneficio',
`${bakeryMetrics.financial.profit_margin.toFixed(1)}%`,
`Beneficio: ${bakeryMetrics.financial.net_profit.toLocaleString()}`,
`Beneficio: ${currencySymbol}${bakeryMetrics.financial.net_profit.toLocaleString()}`,
undefined,
'📈',
'text-purple-600'
@@ -366,7 +368,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
</div>
<div className="text-right">
<p className="font-semibold text-[var(--color-success)]">
{channel.revenue.toLocaleString()}
{currencySymbol}{channel.revenue.toLocaleString()}
</p>
<p className="text-sm text-[var(--text-secondary)]">
Conv. {channel.conversion_rate.toFixed(1)}%
@@ -390,7 +392,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
</div>
<div className="text-right">
<p className="font-semibold text-[var(--color-success)]">
{product.revenue.toLocaleString()}
{currencySymbol}{product.revenue.toLocaleString()}
</p>
<p className="text-sm text-[var(--text-secondary)]">
Margen {product.profit_margin.toFixed(1)}%
@@ -444,7 +446,7 @@ export const AnalyticsDashboard: React.FC<AnalyticsDashboardProps> = ({
</div>
<div className="text-center">
<p className="text-2xl font-bold text-pink-600">
{bakeryMetrics.customer.customer_lifetime_value.toFixed(0)}
{currencySymbol}{bakeryMetrics.customer.customer_lifetime_value.toFixed(0)}
</p>
<p className="text-sm text-[var(--text-secondary)]">Valor Cliente</p>
</div>

View File

@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Button, Input, Card } from '../../ui';
import { useAuthActions, useAuthLoading, useAuthError } from '../../../stores/auth.store';
import { showToast } from '../../../utils/toast';
import { validateEmail } from '../../../utils/validation';
interface LoginFormProps {
onSuccess?: () => void;
@@ -50,10 +51,9 @@ export const LoginForm: React.FC<LoginFormProps> = ({
const validateForm = (): boolean => {
const newErrors: Partial<LoginCredentials> = {};
if (!credentials.email.trim()) {
newErrors.email = t('auth:validation.email_required', 'El email es requerido');
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(credentials.email)) {
newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido');
const emailValidation = validateEmail(credentials.email);
if (!emailValidation.isValid) {
newErrors.email = t('auth:validation.email_invalid', emailValidation.error || 'Por favor, ingrese un email válido');
}
if (!credentials.password) {

View File

@@ -12,6 +12,7 @@ import { Elements } from '@stripe/react-stripe-js';
import { CheckCircle, Clock } from 'lucide-react';
import { usePilotDetection } from '../../../hooks/usePilotDetection';
import { subscriptionService } from '../../../api';
import { validateEmail } from '../../../utils/validation';
// Helper to get Stripe key from runtime config or build-time env
const getStripeKey = (): string => {
@@ -94,6 +95,15 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
const passwordMatchStatus = getPasswordMatchStatus();
// Helper function to determine email validation status (real-time)
const getEmailValidationStatus = () => {
if (!formData.email) return 'empty';
const result = validateEmail(formData.email);
return result.isValid ? 'valid' : 'invalid';
};
const emailValidationStatus = getEmailValidationStatus();
// Load plan metadata when plan changes
useEffect(() => {
const loadPlanMetadata = async () => {
@@ -132,10 +142,9 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
newErrors.full_name = t('auth:validation.field_required', 'El nombre debe tener al menos 2 caracteres');
}
if (!formData.email.trim()) {
newErrors.email = t('auth:validation.email_required', 'El email es requerido');
} else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(formData.email)) {
newErrors.email = t('auth:validation.email_invalid', 'Por favor, ingrese un email válido');
const emailValidation = validateEmail(formData.email);
if (!emailValidation.isValid) {
newErrors.email = t('auth:validation.email_invalid', emailValidation.error || 'Por favor, ingrese un email válido');
}
if (!formData.password) {
@@ -344,22 +353,66 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
}
/>
<Input
type="email"
label={t('auth:register.email', 'Correo Electrónico')}
placeholder="tu.email@ejemplo.com"
value={formData.email}
onChange={handleInputChange('email')}
error={errors.email}
disabled={isLoading}
required
autoComplete="email"
leftIcon={
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
</svg>
}
/>
<div className="relative">
<Input
type="email"
label={t('auth:register.email', 'Correo Electrónico')}
placeholder="tu.email@ejemplo.com"
value={formData.email}
onChange={handleInputChange('email')}
error={errors.email}
disabled={isLoading}
required
autoComplete="email"
className={
emailValidationStatus === 'valid' && formData.email
? 'border-color-success focus:border-color-success ring-color-success'
: emailValidationStatus === 'invalid' && formData.email
? 'border-color-error focus:border-color-error ring-color-error'
: ''
}
leftIcon={
emailValidationStatus === 'valid' && formData.email ? (
<svg className="w-5 h-5 text-color-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
) : emailValidationStatus === 'invalid' && formData.email ? (
<svg className="w-5 h-5 text-color-error" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
) : (
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207" />
</svg>
)
}
/>
{/* Email Validation Status Message */}
{formData.email && (
<div className="mt-2 transition-all duration-300 ease-in-out">
{emailValidationStatus === 'valid' ? (
<div className="flex items-center space-x-2 text-color-success animate-fade-in">
<div className="flex-shrink-0 w-5 h-5 rounded-full bg-color-success/10 flex items-center justify-center">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
<span className="text-sm font-medium">{t('auth:validation.email_valid', 'Email válido')}</span>
</div>
) : (
<div className="flex items-center space-x-2 text-color-error animate-fade-in">
<div className="flex-shrink-0 w-5 h-5 rounded-full bg-color-error/10 flex items-center justify-center">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
</div>
<span className="text-sm font-medium">{t('auth:validation.email_invalid', 'Por favor, ingrese un email válido')}</span>
</div>
)}
</div>
)}
</div>
<Input
type={showPassword ? 'text' : 'password'}
@@ -630,7 +683,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
<div className="flex justify-between items-center pt-3 border-t border-blue-200 dark:border-blue-800">
<span className="text-green-700 dark:text-green-400 font-medium">{t('auth:payment.trial_period', 'Período de prueba:')}</span>
<span className="font-bold text-green-700 dark:text-green-400">
{isPilot ? t('auth:payment.free_months', {count: trialMonths}) : t('auth:payment.free_days', '14 días gratis')}
{isPilot ? t('auth:payment.free_months', {count: trialMonths}) : t('auth:payment.free_days')}
</span>
</div>
)}
@@ -642,7 +695,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
<p className="text-xs text-text-tertiary mt-2 text-center">
{useTrial
? t('auth:payment.billing_message', {price: subscriptionService.formatPrice(selectedPlanMetadata.monthly_price)})
: t('auth:payment.payment_required', 'Tarjeta requerida para validación')
: t('auth:payment.payment_required')
}
</p>
</div>

View File

@@ -19,6 +19,7 @@ import { AlertTriangle, Clock, XCircle, CheckCircle } from 'lucide-react';
import { Button } from '../../ui/Button';
import { Badge } from '../../ui/Badge';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
export interface AutoActionCountdownProps {
actionDescription: string;
@@ -38,6 +39,7 @@ export function AutoActionCountdownComponent({
className = '',
}: AutoActionCountdownProps) {
const { t } = useTranslation('alerts');
const { currencySymbol } = useTenantCurrency();
const [timeRemaining, setTimeRemaining] = useState(countdownSeconds);
const [isCancelling, setIsCancelling] = useState(false);
const [isCancelled, setIsCancelled] = useState(false);
@@ -249,7 +251,7 @@ export function AutoActionCountdownComponent({
{t('auto_action.financial_impact', 'Impact:')}
</span>{' '}
<span className="font-bold" style={{ color: 'var(--text-primary)' }}>
{financialImpactEur.toFixed(2)}
{currencySymbol}{financialImpactEur.toFixed(2)}
</span>
</div>
)}

View File

@@ -6,6 +6,7 @@ import { Badge } from '../../ui/Badge';
import { Button } from '../../ui/Button';
import { useCurrentTenant } from '../../../stores/tenant.store';
import { usePendingApprovalPurchaseOrders, useApprovePurchaseOrder, useRejectPurchaseOrder } from '../../../api/hooks/purchase-orders';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
import {
ShoppingCart,
Clock,
@@ -40,6 +41,7 @@ const PendingPOApprovals: React.FC<PendingPOApprovalsProps> = ({
const { t } = useTranslation(['dashboard']);
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const [approvingPO, setApprovingPO] = useState<string | null>(null);
const [rejectingPO, setRejectingPO] = useState<string | null>(null);
@@ -145,10 +147,7 @@ const PendingPOApprovals: React.FC<PendingPOApprovalsProps> = ({
const formatCurrency = (amount: string, currency: string = 'EUR') => {
const value = parseFloat(amount);
if (currency === 'EUR') {
return `${value.toFixed(2)}`;
}
return `${value.toFixed(2)} ${currency}`;
return `${currencySymbol}${value.toFixed(2)}`;
};
const formatDate = (dateStr: string) => {

View File

@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { EditViewModal } from '../../ui/EditViewModal/EditViewModal';
import { Equipment, MaintenanceHistory } from '../../../api/types/equipment';
import { statusColors } from '../../../styles/colors';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface MaintenanceHistoryModalProps {
isOpen: boolean;
@@ -23,6 +24,7 @@ export const MaintenanceHistoryModal: React.FC<MaintenanceHistoryModalProps> = (
loading = false
}) => {
const { t } = useTranslation(['equipment', 'common']);
const { currencySymbol } = useTenantCurrency();
// Get maintenance type display info with colors and icons
const getMaintenanceTypeInfo = (type: MaintenanceHistory['type']) => {
@@ -127,7 +129,7 @@ export const MaintenanceHistoryModal: React.FC<MaintenanceHistoryModalProps> = (
</div>
<div>
<span className="text-[var(--text-tertiary)]">{t('common:actions.cost', 'Coste')}:</span>
<span className="ml-1 font-medium">{record.cost.toFixed(2)}</span>
<span className="ml-1 font-medium">{currencySymbol}{record.cost.toFixed(2)}</span>
</div>
<div>
<span className="text-[var(--text-tertiary)]">{t('fields.downtime', 'Parada')}:</span>

View File

@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import { useCreateIngredient } from '../../../api/hooks/inventory';
import type { Ingredient } from '../../../api/types/inventory';
import { commonIngredientTemplates } from './ingredientHelpers';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface BatchIngredientRow {
id: string;
@@ -29,6 +30,7 @@ export const BatchAddIngredientsModal: React.FC<BatchAddIngredientsModalProps> =
}) => {
const { t } = useTranslation();
const createIngredient = useCreateIngredient();
const { currencySymbol } = useTenantCurrency();
const [rows, setRows] = useState<BatchIngredientRow[]>([
{ id: '1', name: '', category: 'Baking Ingredients', unit_of_measure: 'kg' },
@@ -269,7 +271,7 @@ export const BatchAddIngredientsModal: React.FC<BatchAddIngredientsModalProps> =
<th className="px-3 py-2 text-left text-xs font-semibold text-[var(--text-primary)]">Categoría *</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-[var(--text-primary)]">Unidad *</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-[var(--text-primary)]">Stock Inicial</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-[var(--text-primary)]">Costo ()</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-[var(--text-primary)]">Costo ({currencySymbol})</th>
<th className="px-3 py-2 text-left text-xs font-semibold text-[var(--text-primary)] w-12"></th>
</tr>
</thead>

View File

@@ -8,6 +8,7 @@ import {
commonIngredientTemplates,
type IngredientTemplate
} from './ingredientHelpers';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface QuickAddIngredientModalProps {
isOpen: boolean;
@@ -26,6 +27,7 @@ export const QuickAddIngredientModal: React.FC<QuickAddIngredientModalProps> = (
}) => {
const { t } = useTranslation();
const createIngredient = useCreateIngredient();
const { currencySymbol } = useTenantCurrency();
// Fetch existing ingredients for duplicate detection
const { data: existingIngredients = [] } = useIngredients(tenantId, {}, {
@@ -478,7 +480,7 @@ export const QuickAddIngredientModal: React.FC<QuickAddIngredientModalProps> = (
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Costo por Unidad ()
Costo por Unidad ({currencySymbol})
</label>
<input
type="number"

View File

@@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '../../../ui/Button';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
import { useCreateIngredient, useClassifyBatch, useAddStock } from '../../../../api/hooks/inventory';
import { useValidateImportFile, useImportSalesData } from '../../../../api/hooks/sales';
import { useSuppliers } from '../../../../api/hooks/suppliers';
@@ -54,6 +55,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
isFirstStep
}) => {
const { t } = useTranslation();
const { currencySymbol } = useTenantCurrency();
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isValidating, setIsValidating] = useState(false);
const [validationResult, setValidationResult] = useState<ImportValidationResponse | null>(null);
@@ -658,7 +660,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
</p>
<div className="mt-2 flex items-center gap-4 text-xs text-[var(--text-secondary)]">
<span>Stock: {item.stock_quantity} {item.unit_of_measure}</span>
<span>Costo: {item.cost_per_unit.toFixed(2)}/{item.unit_of_measure}</span>
<span>Costo: {currencySymbol}{item.cost_per_unit.toFixed(2)}/{item.unit_of_measure}</span>
<span>Caducidad: {item.estimated_shelf_life_days} días</span>
</div>
{item.sales_data && (
@@ -962,7 +964,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
Costo por Unidad ()
Costo por Unidad ({currencySymbol})
</label>
<input
type="number"
@@ -1170,7 +1172,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1">
Costo por Unidad ()
Costo por Unidad ({currencySymbol})
</label>
<input
type="number"

View File

@@ -30,6 +30,7 @@ import { ProductType, ProductCategory } from '../../../api/types/inventory';
import { useCurrentTenant } from '../../../stores/tenant.store';
import { useAuthUser } from '../../../stores/auth.store';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface OrderFormModalProps {
isOpen: boolean;
@@ -47,6 +48,7 @@ export const OrderFormModal: React.FC<OrderFormModalProps> = ({
const user = useAuthUser();
const tenantId = currentTenant?.id || user?.tenant_id || '';
const { t } = useTranslation(['orders', 'common']);
const { currencySymbol } = useTenantCurrency();
// Create enum options using direct i18n
const orderTypeOptions = Object.values(OrderType).map(value => ({
@@ -327,7 +329,7 @@ export const OrderFormModal: React.FC<OrderFormModalProps> = ({
<option value="">Seleccionar producto...</option>
{finishedProducts.map(product => (
<option key={product.id} value={product.id}>
{product.name} - {(product.average_cost || product.standard_cost || 0).toFixed(2)}
{product.name} - {currencySymbol}{(product.average_cost || product.standard_cost || 0).toFixed(2)}
</option>
))}
</select>
@@ -362,7 +364,7 @@ export const OrderFormModal: React.FC<OrderFormModalProps> = ({
<div className="flex-1">
<h4 className="font-medium text-[var(--text-primary)]">{item.product_name}</h4>
<p className="text-sm text-[var(--text-secondary)]">
{item.unit_price.toFixed(2)} × {item.quantity} = {(item.unit_price * item.quantity).toFixed(2)}
{currencySymbol}{item.unit_price.toFixed(2)} × {item.quantity} = {currencySymbol}{(item.unit_price * item.quantity).toFixed(2)}
</p>
</div>
<div className="flex items-center gap-2">

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { ShoppingCart, Plus, Minus, Trash2, X } from 'lucide-react';
import { Card } from '../../ui/Card';
import { Button } from '../../ui/Button';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface CartItem {
id: string;
@@ -29,6 +30,8 @@ export const POSCart: React.FC<POSCartProps> = ({
onClearCart,
taxRate = 0.21, // 21% IVA by default
}) => {
const { currencySymbol } = useTenantCurrency();
// Calculate totals
const subtotal = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
const tax = subtotal * taxRate;
@@ -81,7 +84,7 @@ export const POSCart: React.FC<POSCartProps> = ({
</h4>
<div className="flex items-baseline gap-2 mt-1">
<span className="text-sm font-medium text-[var(--color-primary)]">
{item.price.toFixed(2)}
{currencySymbol}{item.price.toFixed(2)}
</span>
<span className="text-xs text-[var(--text-tertiary)]">c/u</span>
</div>
@@ -128,7 +131,7 @@ export const POSCart: React.FC<POSCartProps> = ({
{/* Item Subtotal */}
<div className="text-right">
<p className="text-base font-bold text-[var(--text-primary)]">
{(item.price * item.quantity).toFixed(2)}
{currencySymbol}{(item.price * item.quantity).toFixed(2)}
</p>
</div>
</div>
@@ -145,7 +148,7 @@ export const POSCart: React.FC<POSCartProps> = ({
<div className="flex justify-between items-center text-sm">
<span className="text-[var(--text-secondary)]">Subtotal:</span>
<span className="font-semibold text-[var(--text-primary)]">
{subtotal.toFixed(2)}
{currencySymbol}{subtotal.toFixed(2)}
</span>
</div>
@@ -153,7 +156,7 @@ export const POSCart: React.FC<POSCartProps> = ({
<div className="flex justify-between items-center text-sm">
<span className="text-[var(--text-secondary)]">IVA ({(taxRate * 100).toFixed(0)}%):</span>
<span className="font-semibold text-[var(--text-primary)]">
{tax.toFixed(2)}
{currencySymbol}{tax.toFixed(2)}
</span>
</div>
@@ -163,7 +166,7 @@ export const POSCart: React.FC<POSCartProps> = ({
<div className="flex justify-between items-center">
<span className="text-lg font-bold text-[var(--text-primary)]">TOTAL:</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">
{total.toFixed(2)}
{currencySymbol}{total.toFixed(2)}
</span>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import { CreditCard, Banknote, ArrowRightLeft, Receipt, User } from 'lucide-reac
import { Card } from '../../ui/Card';
import { Button } from '../../ui/Button';
import { Input } from '../../ui/Input';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface CustomerInfo {
name: string;
@@ -30,6 +31,7 @@ export const POSPayment: React.FC<POSPaymentProps> = ({
onProcessPayment,
disabled = false,
}) => {
const { currencySymbol } = useTenantCurrency();
const [paymentMethod, setPaymentMethod] = useState<'cash' | 'card' | 'transfer'>('cash');
const [cashReceived, setCashReceived] = useState('');
const [customerInfo, setCustomerInfo] = useState<CustomerInfo>({
@@ -193,7 +195,7 @@ export const POSPayment: React.FC<POSPaymentProps> = ({
<Input
type="number"
step="0.01"
placeholder="€0.00"
placeholder={`${currencySymbol}0.00`}
value={cashReceived}
onChange={(e) => setCashReceived(e.target.value)}
className="text-lg font-semibold"
@@ -214,7 +216,7 @@ export const POSPayment: React.FC<POSPaymentProps> = ({
Cambio:
</span>
<span className="text-2xl font-bold" style={{ color: 'var(--color-success-dark)' }}>
{change.toFixed(2)}
{currencySymbol}{change.toFixed(2)}
</span>
</div>
</Card>
@@ -230,7 +232,7 @@ export const POSPayment: React.FC<POSPaymentProps> = ({
}}
>
<p className="text-sm font-medium text-center" style={{ color: 'var(--color-warning-dark)' }}>
Efectivo insuficiente: falta {(total - parseFloat(cashReceived)).toFixed(2)}
Efectivo insuficiente: falta {currencySymbol}{(total - parseFloat(cashReceived)).toFixed(2)}
</p>
</Card>
)}
@@ -247,7 +249,7 @@ export const POSPayment: React.FC<POSPaymentProps> = ({
className="w-full text-lg font-bold py-6 shadow-lg hover:shadow-xl transition-all"
>
<Receipt className="w-6 h-6 mr-2" />
Procesar Venta - {total.toFixed(2)}
Procesar Venta - {currencySymbol}{total.toFixed(2)}
</Button>
</div>
);

View File

@@ -3,6 +3,7 @@ import { Plus, Package } from 'lucide-react';
import { Card } from '../../ui/Card';
import { Button } from '../../ui/Button';
import { Badge } from '../../ui/Badge';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface POSProductCardProps {
id: string;
@@ -28,6 +29,7 @@ export const POSProductCard: React.FC<POSProductCardProps> = ({
onAddToCart,
onClick,
}) => {
const { currencySymbol } = useTenantCurrency();
const remainingStock = stock - cartQuantity;
const isOutOfStock = remainingStock <= 0;
const isLowStock = remainingStock > 0 && remainingStock <= 5;
@@ -97,7 +99,7 @@ export const POSProductCard: React.FC<POSProductCardProps> = ({
{/* Price - Large and prominent */}
<div className="flex items-baseline gap-2">
<span className="text-2xl sm:text-3xl font-bold text-[var(--color-primary)]">
{price.toFixed(2)}
{currencySymbol}{price.toFixed(2)}
</span>
<span className="text-sm text-[var(--text-tertiary)]">c/u</span>
</div>

View File

@@ -6,6 +6,7 @@ import { useCreatePurchaseOrder } from '../../../api/hooks/purchase-orders';
import { useIngredients } from '../../../api/hooks/inventory';
import { useTenantStore } from '../../../stores/tenant.store';
import { suppliersService } from '../../../api/services/suppliers';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
import type { ProcurementRequirementResponse } from '../../../api/types/orders';
import type { PurchaseOrderItemCreate } from '../../../api/services/purchase_orders';
import type { SupplierSummary } from '../../../api/types/suppliers';
@@ -31,6 +32,7 @@ export const CreatePurchaseOrderModal: React.FC<CreatePurchaseOrderModalProps> =
requirements,
onSuccess
}) => {
const { currencySymbol } = useTenantCurrency();
const [loading, setLoading] = useState(false);
const [selectedSupplier, setSelectedSupplier] = useState<string>('');
const [formData, setFormData] = useState<Record<string, any>>({});
@@ -317,7 +319,7 @@ export const CreatePurchaseOrderModal: React.FC<CreatePurchaseOrderModalProps> =
},
{
name: 'unit_price',
label: 'Precio Est. (€)',
label: `Precio Est. (${currencySymbol})`,
type: 'currency',
required: true
}
@@ -362,7 +364,7 @@ export const CreatePurchaseOrderModal: React.FC<CreatePurchaseOrderModalProps> =
},
{
name: 'unit_price',
label: 'Precio Unitario (€)',
label: `Precio Unitario (${currencySymbol})`,
type: 'currency',
required: true,
defaultValue: 0,

View File

@@ -3,6 +3,7 @@ import { Edit, Package, Calendar, Building2 } from 'lucide-react';
import { AddModal } from '../../ui/AddModal/AddModal';
import { useUpdatePurchaseOrder, usePurchaseOrder } from '../../../api/hooks/purchase-orders';
import { useTenantStore } from '../../../stores/tenant.store';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
import type { PurchaseOrderItem } from '../../../api/types/orders';
import { statusColors } from '../../../styles/colors';
@@ -23,6 +24,7 @@ export const ModifyPurchaseOrderModal: React.FC<ModifyPurchaseOrderModalProps> =
poId,
onSuccess
}) => {
const { currencySymbol } = useTenantCurrency();
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<Record<string, any>>({});
@@ -228,7 +230,7 @@ export const ModifyPurchaseOrderModal: React.FC<ModifyPurchaseOrderModalProps> =
},
{
name: 'unit_price',
label: 'Precio Unitario (€)',
label: `Precio Unitario (${currencySymbol})`,
type: 'currency',
required: true,
placeholder: '0.00',

View File

@@ -24,6 +24,7 @@ import { usePurchaseOrder, useUpdatePurchaseOrder } from '../../../api/hooks/pur
import { useUserById } from '../../../api/hooks/user';
import { EditViewModal, EditViewModalSection } from '../../ui/EditViewModal/EditViewModal';
import { Button } from '../../ui/Button';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
import type { PurchaseOrderItem } from '../../../api/services/purchase_orders';
interface UnifiedPurchaseOrderModalProps {
@@ -48,6 +49,7 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
showApprovalActions = false
}) => {
const { t, i18n } = useTranslation(['purchase_orders', 'common']);
const { currencySymbol } = useTenantCurrency();
const { data: po, isLoading, refetch } = usePurchaseOrder(tenantId, poId);
const [mode, setMode] = useState<'view' | 'edit'>(initialMode);
const [showApprovalModal, setShowApprovalModal] = useState(false);
@@ -165,7 +167,7 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
</div>
<div className="text-right">
<p className="font-bold text-lg text-[var(--color-primary-600)]">
{itemTotal.toFixed(2)}
{currencySymbol}{itemTotal.toFixed(2)}
</p>
</div>
</div>
@@ -178,7 +180,7 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
</div>
<div>
<p className="text-[var(--text-secondary)]">{t('unit_price')}</p>
<p className="font-medium text-[var(--text-primary)]">{unitPrice.toFixed(2)}</p>
<p className="font-medium text-[var(--text-primary)]">{currencySymbol}{unitPrice.toFixed(2)}</p>
</div>
</div>
{item.quality_requirements && (
@@ -198,7 +200,7 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
})}
<div className="flex justify-between items-center pt-4 border-t-2 border-[var(--border-primary)]">
<span className="font-semibold text-lg text-[var(--text-primary)]">{t('total')}</span>
<span className="font-bold text-2xl text-[var(--color-primary-600)]">{totalAmount.toFixed(2)}</span>
<span className="font-bold text-2xl text-[var(--color-primary-600)]">{currencySymbol}{totalAmount.toFixed(2)}</span>
</div>
</div>
);
@@ -296,22 +298,22 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
fields: [
...(po.subtotal !== undefined ? [{
label: t('subtotal'),
value: `${formatCurrency(po.subtotal)}`,
value: `${currencySymbol}${formatCurrency(po.subtotal)}`,
type: 'text' as const
}] : []),
...(po.tax_amount !== undefined ? [{
label: t('tax'),
value: `${formatCurrency(po.tax_amount)}`,
value: `${currencySymbol}${formatCurrency(po.tax_amount)}`,
type: 'text' as const
}] : []),
...(po.discount_amount !== undefined ? [{
label: t('discount'),
value: `${formatCurrency(po.discount_amount)}`,
value: `${currencySymbol}${formatCurrency(po.discount_amount)}`,
type: 'text' as const
}] : []),
{
label: t('total_amount'),
value: `${formatCurrency(po.total_amount)}`,
value: `${currencySymbol}${formatCurrency(po.total_amount)}`,
type: 'text' as const,
highlight: true
}
@@ -505,7 +507,7 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
</div>
<div className="text-right">
<p className="font-bold text-lg text-[var(--color-primary-600)]">
{itemTotal.toFixed(2)}
{currencySymbol}{itemTotal.toFixed(2)}
</p>
</div>
</div>
@@ -555,7 +557,7 @@ export const UnifiedPurchaseOrderModal: React.FC<UnifiedPurchaseOrderModalProps>
})}
<div className="flex justify-between items-center pt-4 border-t-2 border-[var(--border-primary)]">
<span className="font-semibold text-lg text-[var(--text-primary)]">{t('total')}</span>
<span className="font-bold text-2xl text-[var(--color-primary-600)]">{totalAmount.toFixed(2)}</span>
<span className="font-bold text-2xl text-[var(--color-primary-600)]">{currencySymbol}{totalAmount.toFixed(2)}</span>
</div>
</div>
);

View File

@@ -4,6 +4,7 @@ import { Brain, TrendingUp, AlertTriangle, Target, Zap, DollarSign, Clock } from
import { AnalyticsWidget } from '../AnalyticsWidget';
import { Badge, Button } from '../../../../ui';
import { useCurrentTenant } from '../../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../../hooks/useTenantCurrency';
interface AIInsight {
id: string;
@@ -27,6 +28,7 @@ interface AIInsight {
export const AIInsightsWidget: React.FC = () => {
const { t } = useTranslation('production');
const currentTenant = useCurrentTenant();
const { currencySymbol } = useTenantCurrency();
// Mock AI insights data - replace with real AI API call
const aiInsights: AIInsight[] = [
@@ -172,7 +174,7 @@ export const AIInsightsWidget: React.FC = () => {
const formatImpactValue = (impact: AIInsight['impact']) => {
switch (impact.unit) {
case 'euros': return `${impact.value}`;
case 'euros': return `${currencySymbol}${impact.value}`;
case 'percentage': return `${impact.value}%`;
case 'hours': return `${impact.value}h`;
case 'units': return `${impact.value} unidades`;
@@ -222,9 +224,9 @@ export const AIInsightsWidget: React.FC = () => {
</div>
<div className="text-center p-4 bg-[var(--color-success)]/10 rounded-lg border border-[var(--color-success)]/20 hover:border-[var(--color-success)]/40 transition-colors">
<div className="w-8 h-8 mx-auto bg-[var(--color-success)]/20 rounded-full flex items-center justify-center mb-2">
<span className="text-[var(--color-success)] font-bold text-sm"></span>
<span className="text-[var(--color-success)] font-bold text-sm">{currencySymbol}</span>
</div>
<p className="text-2xl font-bold text-[var(--color-success)]">{totalPotentialSavings}</p>
<p className="text-2xl font-bold text-[var(--color-success)]">{currencySymbol}{totalPotentialSavings}</p>
<p className="text-sm text-[var(--text-secondary)] font-medium">{t('ai.stats.potential_savings')}</p>
</div>
<div className="text-center p-4 bg-[var(--color-info)]/10 rounded-lg border border-[var(--color-info)]/20 hover:border-[var(--color-info)]/40 transition-colors">
@@ -371,7 +373,7 @@ export const AIInsightsWidget: React.FC = () => {
</p>
<p className="text-xs text-[var(--text-secondary)]">
{implementedInsights.length} {t('ai.performance.insights_implemented')}
{totalPotentialSavings > 0 && `, ${totalPotentialSavings} ${t('ai.performance.in_savings_identified')}`}
{totalPotentialSavings > 0 && `, ${currencySymbol}${totalPotentialSavings} ${t('ai.performance.in_savings_identified')}`}
</p>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { AnalyticsChart, ChartSeries } from '../AnalyticsChart';
import { Button } from '../../../../ui';
import { useActiveBatches } from '../../../../../api/hooks/production';
import { useCurrentTenant } from '../../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../../hooks/useTenantCurrency';
interface ProductCostData {
product: string;
@@ -21,6 +22,7 @@ export const CostPerUnitWidget: React.FC = () => {
const { t } = useTranslation('production');
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const { data: batchesData, isLoading, error } = useActiveBatches(tenantId);
const batches = batchesData?.batches || [];
@@ -162,7 +164,7 @@ export const CostPerUnitWidget: React.FC = () => {
<div className="flex items-center justify-center space-x-2 mb-2">
<DollarSign className="w-5 h-5 text-[var(--color-primary)]" />
<span className="text-lg font-bold text-[var(--text-primary)]">
{averageCostPerUnit.toFixed(2)}
{currencySymbol}{averageCostPerUnit.toFixed(2)}
</span>
</div>
<p className="text-sm text-[var(--text-secondary)]">{t('cost.average_cost_per_unit')}</p>
@@ -171,7 +173,7 @@ export const CostPerUnitWidget: React.FC = () => {
<div className="flex items-center justify-center space-x-2 mb-2">
<TrendingUp className="w-5 h-5 text-[var(--color-primary)]" />
<span className="text-lg font-bold text-[var(--text-primary)]">
{totalCosts.toFixed(0)}
{currencySymbol}{totalCosts.toFixed(0)}
</span>
</div>
<p className="text-sm text-[var(--text-secondary)]">{t('cost.total_production_cost')}</p>
@@ -221,7 +223,7 @@ export const CostPerUnitWidget: React.FC = () => {
</span>
</div>
<span className="text-lg font-bold text-[var(--text-primary)]">
{item.costPerUnit.toFixed(2)}
{currencySymbol}{item.costPerUnit.toFixed(2)}
</span>
</div>
@@ -229,13 +231,13 @@ export const CostPerUnitWidget: React.FC = () => {
<div>
<p className="text-[var(--text-secondary)]">{t('cost.estimated')}</p>
<p className="font-semibold text-[var(--text-primary)]">
{item.estimatedCost.toFixed(2)}
{currencySymbol}{item.estimatedCost.toFixed(2)}
</p>
</div>
<div>
<p className="text-[var(--text-secondary)]">{t('cost.actual')}</p>
<p className="font-semibold text-[var(--text-primary)]">
{item.actualCost.toFixed(2)}
{currencySymbol}{item.actualCost.toFixed(2)}
</p>
</div>
<div>

View File

@@ -4,6 +4,7 @@ import { Calendar, Clock, Wrench, AlertCircle, CheckCircle2 } from 'lucide-react
import { AnalyticsWidget } from '../AnalyticsWidget';
import { Badge, Button } from '../../../../ui';
import { useCurrentTenant } from '../../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../../hooks/useTenantCurrency';
interface MaintenanceTask {
id: string;
@@ -24,6 +25,7 @@ interface MaintenanceTask {
export const MaintenanceScheduleWidget: React.FC = () => {
const { t } = useTranslation('production');
const currentTenant = useCurrentTenant();
const { currencySymbol } = useTenantCurrency();
// Mock maintenance data - replace with real API call
const maintenanceTasks: MaintenanceTask[] = [
@@ -185,9 +187,9 @@ export const MaintenanceScheduleWidget: React.FC = () => {
</div>
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="w-8 h-8 mx-auto bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center mb-2">
<span className="text-green-600 font-bold text-sm"></span>
<span className="text-green-600 font-bold text-sm">{currencySymbol}</span>
</div>
<p className="text-2xl font-bold text-[var(--text-primary)]">{totalCost}</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">{currencySymbol}{totalCost}</p>
<p className="text-sm text-[var(--text-secondary)]">{t('equipment.maintenance.total_cost')}</p>
</div>
</div>
@@ -234,7 +236,7 @@ export const MaintenanceScheduleWidget: React.FC = () => {
<div className="flex items-center space-x-4 text-xs text-[var(--text-secondary)]">
<span>{t('equipment.maintenance.scheduled')}: {formatDate(task.scheduledDate)}</span>
<span>{t('equipment.maintenance.duration')}: {task.estimatedDuration}h</span>
{task.cost && <span>{t('equipment.maintenance.cost')}: {task.cost}</span>}
{task.cost && <span>{t('equipment.maintenance.cost')}: {currencySymbol}{task.cost}</span>}
{task.technician && <span>{t('equipment.maintenance.technician')}: {task.technician}</span>}
</div>
</div>

View File

@@ -5,6 +5,7 @@ import { AnalyticsWidget } from '../AnalyticsWidget';
import { AnalyticsChart, ChartSeries } from '../AnalyticsChart';
import { Badge, Button } from '../../../../ui';
import { useCurrentTenant } from '../../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../../hooks/useTenantCurrency';
interface PredictiveMaintenanceAlert {
id: string;
@@ -34,6 +35,7 @@ interface PredictiveMaintenanceAlert {
export const PredictiveMaintenanceWidget: React.FC = () => {
const { t } = useTranslation('production');
const currentTenant = useCurrentTenant();
const { currencySymbol } = useTenantCurrency();
// Mock predictive maintenance data - replace with real ML API call
const maintenanceAlerts: PredictiveMaintenanceAlert[] = [
@@ -239,9 +241,9 @@ export const PredictiveMaintenanceWidget: React.FC = () => {
</div>
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
<div className="w-8 h-8 mx-auto bg-orange-100 dark:bg-orange-900/20 rounded-full flex items-center justify-center mb-2">
<span className="text-orange-600 font-bold text-sm"></span>
<span className="text-orange-600 font-bold text-sm">{currencySymbol}</span>
</div>
<p className="text-2xl font-bold text-[var(--text-primary)]">{totalEstimatedCost}</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">{currencySymbol}{totalEstimatedCost}</p>
<p className="text-sm text-[var(--text-secondary)]">{t('ai.predictive_maintenance.estimated_cost')}</p>
</div>
<div className="text-center p-4 bg-[var(--bg-secondary)] rounded-lg">
@@ -365,7 +367,7 @@ export const PredictiveMaintenanceWidget: React.FC = () => {
<div className="flex items-center space-x-4 text-xs">
<span className="flex items-center space-x-1">
<span className="w-2 h-2 bg-orange-500 rounded-full"></span>
<span>{t('ai.predictive_maintenance.estimated_cost')}: {alert.estimatedCost}</span>
<span>{t('ai.predictive_maintenance.estimated_cost')}: {currencySymbol}{alert.estimatedCost}</span>
</span>
<span className="flex items-center space-x-1">
<span className="w-2 h-2 bg-red-500 rounded-full"></span>

View File

@@ -6,6 +6,7 @@ import { AnalyticsChart, ChartSeries } from '../AnalyticsChart';
import { Button, Badge } from '../../../../ui';
import { useActiveBatches } from '../../../../../api/hooks/production';
import { useCurrentTenant } from '../../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../../hooks/useTenantCurrency';
interface DefectType {
type: string;
@@ -20,6 +21,7 @@ export const TopDefectTypesWidget: React.FC = () => {
const { t } = useTranslation('production');
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const { data: batchesData, isLoading, error } = useActiveBatches(tenantId);
const batches = batchesData?.batches || [];
@@ -193,7 +195,7 @@ export const TopDefectTypesWidget: React.FC = () => {
<div className="p-4 bg-[var(--bg-secondary)] rounded-lg text-center">
<div className="flex items-center justify-center space-x-2 mb-1">
<span className="text-2xl font-bold text-red-600">
{totalDefectCost.toFixed(0)}
{currencySymbol}{totalDefectCost.toFixed(0)}
</span>
</div>
<p className="text-sm text-[var(--text-secondary)]">{t('quality.estimated_cost')}</p>
@@ -229,7 +231,7 @@ export const TopDefectTypesWidget: React.FC = () => {
<div className="flex items-center space-x-3 text-xs text-[var(--text-secondary)]">
<span>{defect.count} {t('quality.incidents')}</span>
<span></span>
<span>{defect.estimatedCost.toFixed(2)} {t('quality.cost')}</span>
<span>{currencySymbol}{defect.estimatedCost.toFixed(2)} {t('quality.cost')}</span>
<span className={getTrendColor(defect.trend)}>
{getTrendIcon(defect.trend)} {t(`quality.trend.${defect.trend}`)}
</span>

View File

@@ -6,6 +6,7 @@ import { AnalyticsChart, ChartSeries } from '../AnalyticsChart';
import { Button, Badge } from '../../../../ui';
import { useActiveBatches } from '../../../../../api/hooks/production';
import { useCurrentTenant } from '../../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../../hooks/useTenantCurrency';
interface WasteSource {
source: string;
@@ -19,6 +20,7 @@ export const WasteDefectTrackerWidget: React.FC = () => {
const { t } = useTranslation('production');
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const { data: batchesData, isLoading, error } = useActiveBatches(tenantId);
const batches = batchesData?.batches || [];
@@ -202,7 +204,7 @@ export const WasteDefectTrackerWidget: React.FC = () => {
<div className="flex items-center justify-center space-x-2 mb-1">
<TrendingDown className="w-5 h-5 text-[var(--color-success)]" />
<span className="text-2xl font-bold text-[var(--color-success)]">
{totalWasteCost.toFixed(0)}
{currencySymbol}{totalWasteCost.toFixed(0)}
</span>
</div>
<p className="text-xs text-[var(--text-secondary)] font-medium">{t('cost.waste_cost')}</p>
@@ -241,7 +243,7 @@ export const WasteDefectTrackerWidget: React.FC = () => {
{source.source}
</p>
<p className="text-xs text-[var(--text-secondary)]">
{source.count} {t('common.units')} {source.cost.toFixed(2)}
{source.count} {t('common.units')} {currencySymbol}{source.cost.toFixed(2)}
</p>
</div>
</div>

View File

@@ -9,13 +9,14 @@ import {
Tooltip,
Modal
} from '../../ui';
import {
import {
SalesRecord,
SalesChannel,
PaymentMethod
} from '../../../types/sales.types';
import { salesService } from '../../../api/services/sales.service';
import { useSales } from '../../../hooks/api/useSales';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
// Customer interfaces
interface Customer {
@@ -221,6 +222,8 @@ export const CustomerInfo: React.FC<CustomerInfoProps> = ({
allowEditing = true,
className = ''
}) => {
const { currencySymbol } = useTenantCurrency();
// State
const [customer, setCustomer] = useState<Customer | null>(null);
const [customerStats, setCustomerStats] = useState<CustomerStats | null>(null);
@@ -531,7 +534,7 @@ export const CustomerInfo: React.FC<CustomerInfoProps> = ({
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Total Gastado</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">
{customerStats.total_spent.toFixed(2)}
{currencySymbol}{customerStats.total_spent.toFixed(2)}
</p>
</div>
<div className="p-2 bg-[var(--color-info)]/10 rounded-lg">
@@ -563,7 +566,7 @@ export const CustomerInfo: React.FC<CustomerInfoProps> = ({
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Ticket Promedio</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">
{customerStats.average_order_value.toFixed(2)}
{currencySymbol}{customerStats.average_order_value.toFixed(2)}
</p>
</div>
<div className="p-2 bg-yellow-100 rounded-lg">
@@ -854,7 +857,7 @@ export const CustomerInfo: React.FC<CustomerInfoProps> = ({
<div className="flex items-center space-x-4">
<div className="text-right">
<p className="font-medium text-[var(--text-primary)]">{order.total.toFixed(2)}</p>
<p className="font-medium text-[var(--text-primary)]">{currencySymbol}{order.total.toFixed(2)}</p>
<p className="text-sm text-[var(--text-secondary)]">{order.items_count} artículos</p>
</div>
<Badge color={order.status === OrderStatus.DELIVERED ? 'green' : 'blue'} variant="soft">

View File

@@ -13,6 +13,7 @@ import {
PaymentMethod
} from '../../../types/sales.types';
import { salesService } from '../../../api/services/sales.service';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
// Order form interfaces
interface Product {
@@ -274,6 +275,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
showPricing = true,
className = ''
}) => {
const { currencySymbol } = useTenantCurrency();
// Form data state
const [orderData, setOrderData] = useState<OrderFormData>({
customer: initialCustomer,
@@ -687,7 +689,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
<div className="flex-1">
<h4 className="font-medium text-[var(--text-primary)]">{item.product_name}</h4>
<p className="text-sm text-[var(--text-secondary)]">
{item.unit_price.toFixed(2)} × {item.quantity} = {item.total_price.toFixed(2)}
{currencySymbol}{item.unit_price.toFixed(2)} × {item.quantity} = {currencySymbol}{item.total_price.toFixed(2)}
</p>
{item.special_instructions && (
<p className="text-sm text-[var(--color-primary)] mt-1">
@@ -844,10 +846,10 @@ export const OrderForm: React.FC<OrderFormProps> = ({
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
<p className="text-sm text-yellow-800">
💡 <strong>Envío gratuito</strong> en pedidos superiores a 25.
Tu pedido: {orderData.subtotal.toFixed(2)}
💡 <strong>Envío gratuito</strong> en pedidos superiores a {currencySymbol}25.
Tu pedido: {currencySymbol}{orderData.subtotal.toFixed(2)}
{orderData.subtotal < 25 && (
<span> - Faltan {(25 - orderData.subtotal).toFixed(2)} para envío gratuito</span>
<span> - Faltan {currencySymbol}{(25 - orderData.subtotal).toFixed(2)} para envío gratuito</span>
)}
</p>
</div>
@@ -953,39 +955,39 @@ export const OrderForm: React.FC<OrderFormProps> = ({
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-[var(--text-secondary)]">Subtotal</span>
<span className="font-medium">{orderData.subtotal.toFixed(2)}</span>
<span className="font-medium">{currencySymbol}{orderData.subtotal.toFixed(2)}</span>
</div>
{orderData.discount_amount > 0 && (
<div className="flex justify-between text-sm text-[var(--color-success)]">
<span>Descuento{orderData.discount_code && ` (${orderData.discount_code})`}</span>
<span>-{orderData.discount_amount.toFixed(2)}</span>
<span>-{currencySymbol}{orderData.discount_amount.toFixed(2)}</span>
</div>
)}
{orderData.delivery_fee > 0 && (
<div className="flex justify-between text-sm">
<span className="text-[var(--text-secondary)]">Gastos de envío</span>
<span className="font-medium">{orderData.delivery_fee.toFixed(2)}</span>
<span className="font-medium">{currencySymbol}{orderData.delivery_fee.toFixed(2)}</span>
</div>
)}
{orderData.loyalty_points_to_use > 0 && (
<div className="flex justify-between text-sm text-[var(--color-success)]">
<span>Puntos utilizados ({orderData.loyalty_points_to_use})</span>
<span>-{(orderData.loyalty_points_to_use * 0.01).toFixed(2)}</span>
<span>-{currencySymbol}{(orderData.loyalty_points_to_use * 0.01).toFixed(2)}</span>
</div>
)}
<div className="flex justify-between text-sm">
<span className="text-[var(--text-secondary)]">IVA ({(orderData.tax_rate * 100).toFixed(0)}%)</span>
<span className="font-medium">{orderData.tax_amount.toFixed(2)}</span>
<span className="font-medium">{currencySymbol}{orderData.tax_amount.toFixed(2)}</span>
</div>
<div className="border-t pt-3">
<div className="flex justify-between">
<span className="text-lg font-semibold text-[var(--text-primary)]">Total</span>
<span className="text-lg font-bold text-[var(--color-info)]">{orderData.total_amount.toFixed(2)}</span>
<span className="text-lg font-bold text-[var(--color-info)]">{currencySymbol}{orderData.total_amount.toFixed(2)}</span>
</div>
</div>
</div>
@@ -1025,7 +1027,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
</span>
</div>
<p className="text-xs text-[var(--text-tertiary)] mt-1">
Ahorro: {(orderData.loyalty_points_to_use * 0.01).toFixed(2)}
Ahorro: {currencySymbol}{(orderData.loyalty_points_to_use * 0.01).toFixed(2)}
</p>
</div>
)}
@@ -1135,7 +1137,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
{product.category}
</Badge>
<span className="text-lg font-semibold text-[var(--color-info)]">
{product.price.toFixed(2)}
{currencySymbol}{product.price.toFixed(2)}
</span>
</div>

View File

@@ -13,6 +13,7 @@ import {
import { SalesDataResponse } from '../../../api/types/sales';
import { salesService } from '../../../api/services/sales';
import { useSalesRecords } from '../../../api/hooks/sales';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
// Define missing types for backwards compatibility
type SalesRecord = SalesDataResponse;
@@ -106,6 +107,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
initialFilters = {}
}) => {
const { t } = useTranslation(['sales']);
const { currencySymbol } = useTenantCurrency();
// Translation helper functions
const getStatusLabel = (status: OrderStatus) => {
@@ -316,10 +318,10 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
sortable: true,
render: (order: Order) => (
<div className="text-right">
<div className="font-semibold">{order.total_revenue.toFixed(2)}</div>
<div className="font-semibold">{currencySymbol}{order.total_revenue.toFixed(2)}</div>
{order.discount_applied > 0 && (
<div className="text-sm text-[var(--color-success)]">
-{order.discount_applied.toFixed(2)}
-{currencySymbol}{order.discount_applied.toFixed(2)}
</div>
)}
</div>
@@ -590,7 +592,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
...prev,
min_total: e.target.value ? parseFloat(e.target.value) : undefined
}))}
placeholder="€0.00"
placeholder={`${currencySymbol}0.00`}
/>
<Input
label="Total máximo"
@@ -601,7 +603,7 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
...prev,
max_total: e.target.value ? parseFloat(e.target.value) : undefined
}))}
placeholder="€999.99"
placeholder={`${currencySymbol}999.99`}
/>
</div>
</div>
@@ -781,8 +783,8 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
)}
</div>
<div className="text-right">
<div className="font-medium">{selectedOrder.unit_price.toFixed(2)} × {selectedOrder.quantity_sold}</div>
<div className="text-sm text-[var(--text-secondary)]">{selectedOrder.total_revenue.toFixed(2)}</div>
<div className="font-medium">{currencySymbol}{selectedOrder.unit_price.toFixed(2)} × {selectedOrder.quantity_sold}</div>
<div className="text-sm text-[var(--text-secondary)]">{currencySymbol}{selectedOrder.total_revenue.toFixed(2)}</div>
</div>
</div>
</div>
@@ -800,18 +802,18 @@ export const OrdersTable: React.FC<OrdersTableProps> = ({
<div className="border-t pt-4">
<div className="flex justify-between items-center text-lg font-semibold">
<span>Total del Pedido:</span>
<span>{selectedOrder.total_revenue.toFixed(2)}</span>
<span>{currencySymbol}{selectedOrder.total_revenue.toFixed(2)}</span>
</div>
{selectedOrder.discount_applied > 0 && (
<div className="flex justify-between items-center text-sm text-[var(--color-success)]">
<span>Descuento aplicado:</span>
<span>-{selectedOrder.discount_applied.toFixed(2)}</span>
<span>-{currencySymbol}{selectedOrder.discount_applied.toFixed(2)}</span>
</div>
)}
{selectedOrder.tax_amount > 0 && (
<div className="flex justify-between items-center text-sm text-[var(--text-secondary)]">
<span>IVA incluido:</span>
<span>{selectedOrder.tax_amount.toFixed(2)}</span>
<span>{currencySymbol}{selectedOrder.tax_amount.toFixed(2)}</span>
</div>
)}
</div>

View File

@@ -10,6 +10,7 @@ import { SalesAnalytics } from '../../../api/types/sales';
import { ProductPerformance } from '../analytics/types';
import { salesService } from '../../../api/services/sales';
import { useSalesAnalytics } from '../../../api/hooks/sales';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
// Define missing types
export enum PeriodType {
@@ -137,6 +138,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
showExport = true,
className = ''
}) => {
const { currencySymbol } = useTenantCurrency();
// State
const [analytics, setAnalytics] = useState<ExtendedSalesAnalytics | null>(null);
const [loading, setLoading] = useState(false);
@@ -247,7 +249,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
),
datasets: [
{
label: 'Ingresos (€)',
label: `Ingresos (${currencySymbol})`,
data: analytics.daily_trends.map(trend => trend.revenue),
backgroundColor: chartType === ChartType.PIE ?
generateColors(analytics.daily_trends.length) : Colors.primary,
@@ -291,7 +293,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
),
datasets: [
{
label: 'Ticket Promedio (€)',
label: `Ticket Promedio (${currencySymbol})`,
data: analytics.daily_trends.map(trend => trend.average_order_value),
backgroundColor: chartType === ChartType.PIE ?
generateColors(analytics.daily_trends.length) : Colors.tertiary,
@@ -309,7 +311,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
labels: topProducts.map(product => product.product_name),
datasets: [
{
label: 'Ingresos por Producto (€)',
label: `Ingresos por Producto (${currencySymbol})`,
data: topProducts.map(product => product.total_revenue),
backgroundColor: generateColors(topProducts.length),
borderColor: Colors.primary,
@@ -323,7 +325,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
labels: analytics.hourly_patterns.map(pattern => `${pattern.hour}:00`),
datasets: [
{
label: 'Ventas Promedio por Hora (€)',
label: `Ventas Promedio por Hora (${currencySymbol})`,
data: analytics.hourly_patterns.map(pattern => pattern.average_sales),
backgroundColor: Colors.secondary,
borderColor: Colors.secondary,
@@ -474,7 +476,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
fontSize="12"
fill={Colors.text}
>
{(minValue + range * (1 - ratio)).toLocaleString('es-ES', { maximumFractionDigits: 0 })}
{currencySymbol}{(minValue + range * (1 - ratio)).toLocaleString('es-ES', { maximumFractionDigits: 0 })}
</text>
</g>
);
@@ -558,7 +560,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
strokeWidth={2}
>
<title>
{chartData.labels[index]}: {dataset.data[index].toLocaleString('es-ES', { minimumFractionDigits: 2 })}
{chartData.labels[index]}: {currencySymbol}{dataset.data[index].toLocaleString('es-ES', { minimumFractionDigits: 2 })}
</title>
</circle>
))}
@@ -751,7 +753,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Ingresos Totales</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">
{summaryStats.totalRevenue.toLocaleString('es-ES', { minimumFractionDigits: 2 })}
{currencySymbol}{summaryStats.totalRevenue.toLocaleString('es-ES', { minimumFractionDigits: 2 })}
</p>
</div>
<div className={`flex items-center ${summaryStats.growthRate >= 0 ? 'text-[var(--color-success)]' : 'text-[var(--color-error)]'}`}>
@@ -777,7 +779,7 @@ export const SalesChart: React.FC<SalesChartProps> = ({
<div>
<p className="text-sm font-medium text-[var(--text-secondary)]">Ticket Promedio</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">
{summaryStats.avgOrderValue.toLocaleString('es-ES', { minimumFractionDigits: 2 })}
{currencySymbol}{summaryStats.avgOrderValue.toLocaleString('es-ES', { minimumFractionDigits: 2 })}
</p>
</div>
</Card>

View File

@@ -9,6 +9,7 @@ import {
import { useIngredients } from '../../../../api/hooks/inventory';
import type { SupplierPriceListCreate, SupplierPriceListResponse } from '../../../../api/types/suppliers';
import { QuickAddIngredientModal } from '../../inventory/QuickAddIngredientModal';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
interface SupplierProductManagerProps {
tenantId: string;
@@ -30,6 +31,7 @@ export const SupplierProductManager: React.FC<SupplierProductManagerProps> = ({
supplierName
}) => {
const { t } = useTranslation();
const { currencySymbol } = useTenantCurrency();
// Fetch existing price lists for this supplier
const { data: priceLists = [], isLoading: priceListsLoading } = useSupplierPriceLists(
@@ -236,7 +238,7 @@ export const SupplierProductManager: React.FC<SupplierProductManagerProps> = ({
{getProductName(priceList.inventory_product_id)}
</span>
<span className="text-[var(--text-secondary)] ml-2">
{Number(priceList.unit_price || 0).toFixed(2)}/{priceList.unit_of_measure}
{currencySymbol}{Number(priceList.unit_price || 0).toFixed(2)}/{priceList.unit_of_measure}
</span>
{priceList.minimum_order_quantity && priceList.minimum_order_quantity > 1 && (
<span className="text-xs text-[var(--text-secondary)] ml-2">
@@ -319,7 +321,7 @@ export const SupplierProductManager: React.FC<SupplierProductManagerProps> = ({
<div className="ml-6 mt-2 grid grid-cols-3 gap-2 p-2 bg-[var(--bg-primary)] rounded">
<div>
<label className="block text-xs font-medium text-[var(--text-primary)] mb-1">
{t('setup_wizard:suppliers.unit_price', 'Price')} () *
{t('setup_wizard:suppliers.unit_price', 'Price')} ({currencySymbol}) *
</label>
<input
type="number"

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { Truck } from 'lucide-react';
import type { WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import type { SupplierCreate, PaymentTerms, DeliverySchedule } from '../../../../api/types/suppliers';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
interface SupplierDeliveryStepProps extends WizardStepProps {
supplierData: Partial<SupplierCreate>;
@@ -14,6 +15,8 @@ export const SupplierDeliveryStep: React.FC<SupplierDeliveryStepProps> = ({
onNext,
onBack
}) => {
const { currencySymbol } = useTenantCurrency();
const handleFieldChange = (field: keyof SupplierCreate, value: any) => {
onUpdate({ ...supplierData, [field]: value });
};
@@ -87,7 +90,7 @@ export const SupplierDeliveryStep: React.FC<SupplierDeliveryStepProps> = ({
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-[var(--text-primary)] mb-1.5">
Pedido Mínimo ()
Pedido Mínimo ({currencySymbol})
</label>
<input
type="number"

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { FileText, CheckCircle2, Users, Truck, Award } from 'lucide-react';
import type { WizardStepProps } from '../../../ui/WizardModal/WizardModal';
import type { SupplierCreate } from '../../../../api/types/suppliers';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
interface SupplierReviewStepProps extends WizardStepProps {
supplierData: Partial<SupplierCreate>;
@@ -14,6 +15,8 @@ export const SupplierReviewStep: React.FC<SupplierReviewStepProps> = ({
onNext,
onBack
}) => {
const { currencySymbol } = useTenantCurrency();
const handleFieldChange = (field: keyof SupplierCreate, value: any) => {
onUpdate({ ...supplierData, [field]: value });
};
@@ -129,7 +132,7 @@ export const SupplierReviewStep: React.FC<SupplierReviewStepProps> = ({
<Truck className="w-4 h-4 text-[var(--text-secondary)] mt-0.5 flex-shrink-0" />
<div>
<p className="text-xs text-[var(--text-tertiary)]">Pedido Mínimo</p>
<p className="text-sm font-medium text-[var(--text-primary)]">{supplierData.minimum_order_value}</p>
<p className="text-sm font-medium text-[var(--text-primary)]">{currencySymbol}{supplierData.minimum_order_value}</p>
</div>
</div>
)}

View File

@@ -15,6 +15,7 @@ import Card from '../../ui/Card/Card';
import { Button, Badge } from '../../ui';
import { useSustainabilityWidget } from '../../../api/hooks/sustainability';
import { useCurrentTenant } from '../../../stores/tenant.store';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
interface SustainabilityWidgetProps {
days?: number;
@@ -30,6 +31,7 @@ export const SustainabilityWidget: React.FC<SustainabilityWidgetProps> = ({
const { t } = useTranslation(['sustainability', 'common']);
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const { data, isLoading, error } = useSustainabilityWidget(tenantId, days, {
enabled: !!tenantId
@@ -205,7 +207,7 @@ export const SustainabilityWidget: React.FC<SustainabilityWidgetProps> = ({
{t('sustainability:financial.potential_savings', 'Potential Monthly Savings')}
</p>
<p className="text-2xl font-bold text-green-600 dark:text-green-400">
{data.financial_savings_eur.toFixed(2)}
{currencySymbol}{data.financial_savings_eur.toFixed(2)}
</p>
</div>
<TreeDeciduous className="w-10 h-10 text-green-600/30 dark:text-green-400/30" />

View File

@@ -20,6 +20,7 @@ import { useTenant } from '../../../../stores/tenant.store';
import OrdersService from '../../../../api/services/orders';
import { inventoryService } from '../../../../api/services/inventory';
import { ProductType } from '../../../../api/types/inventory';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
// Step 1: Customer Selection
const CustomerSelectionStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
@@ -293,6 +294,7 @@ const OrderItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
const data = dataRef?.current || {};
const { t } = useTranslation('wizards');
const { currentTenant } = useTenant();
const { currencySymbol } = useTenantCurrency();
const [products, setProducts] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -452,7 +454,7 @@ const OrderItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
<option value="">{t('customerOrder.orderItems.selectProduct')}</option>
{products.map((product) => (
<option key={product.id} value={product.id}>
{product.name} - {(product.average_cost || product.last_purchase_price || 0).toFixed(2)} / {product.unit_of_measure}
{product.name} - {currencySymbol}{(product.average_cost || product.last_purchase_price || 0).toFixed(2)} / {product.unit_of_measure}
</option>
))}
</select>
@@ -502,7 +504,7 @@ const OrderItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
<div className="pt-2 border-t border-[var(--border-primary)] text-sm">
<span className="font-semibold text-[var(--text-primary)]">
{t('customerOrder.orderItems.subtotal')}: {item.subtotal.toFixed(2)}
{t('customerOrder.orderItems.subtotal')}: {currencySymbol}{item.subtotal.toFixed(2)}
</span>
</div>
</div>
@@ -515,7 +517,7 @@ const OrderItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
<div className="flex justify-between items-center">
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('customerOrder.messages.orderTotal')}:</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">
{calculateTotal().toFixed(2)}
{currencySymbol}{calculateTotal().toFixed(2)}
</span>
</div>
</div>
@@ -531,6 +533,7 @@ const OrderItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
const DeliveryPaymentStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
const data = dataRef?.current || {};
const { t } = useTranslation('wizards');
const { currencySymbol } = useTenantCurrency();
// Helper to get field value with defaults
const getValue = (field: string, defaultValue: any = '') => {
@@ -820,7 +823,7 @@ const DeliveryPaymentStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange
<div className="flex justify-between">
<span className="text-[var(--text-secondary)]">{t('customerOrder.messages.total')}:</span>
<span className="font-semibold text-lg text-[var(--color-primary)]">
{data.totalAmount?.toFixed(2) || '0.00'}
{currencySymbol}{data.totalAmount?.toFixed(2) || '0.00'}
</span>
</div>
</div>

View File

@@ -4,6 +4,7 @@ import { WizardStep, WizardStepProps } from '../../../ui/WizardModal/WizardModal
import { AdvancedOptionsSection } from '../../../ui/AdvancedOptionsSection';
import Tooltip from '../../../ui/Tooltip/Tooltip';
import { Info, Package, ShoppingBag } from 'lucide-react';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
// STEP 1: Product Type Selection with advanced fields
const ProductTypeStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
@@ -197,24 +198,31 @@ const BasicInfoStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
<>
<option value="">{t('inventory.ingredientCategories.select')}</option>
<option value="flour">{t('inventory.ingredientCategories.flour')}</option>
<option value="yeast">{t('inventory.ingredientCategories.yeast')}</option>
<option value="dairy">{t('inventory.ingredientCategories.dairy')}</option>
<option value="eggs">{t('inventory.ingredientCategories.eggs')}</option>
<option value="sugar">{t('inventory.ingredientCategories.sugar')}</option>
<option value="fats">{t('inventory.ingredientCategories.fats')}</option>
<option value="sweeteners">{t('inventory.ingredientCategories.sweeteners')}</option>
<option value="additives">{t('inventory.ingredientCategories.additives')}</option>
<option value="fruits">{t('inventory.ingredientCategories.fruits')}</option>
<option value="nuts">{t('inventory.ingredientCategories.nuts')}</option>
<option value="salt">{t('inventory.ingredientCategories.salt')}</option>
<option value="spices">{t('inventory.ingredientCategories.spices')}</option>
<option value="leavening">{t('inventory.ingredientCategories.leavening')}</option>
<option value="additives">{t('inventory.ingredientCategories.additives')}</option>
<option value="packaging">{t('inventory.ingredientCategories.packaging')}</option>
<option value="cleaning">{t('inventory.ingredientCategories.cleaning')}</option>
<option value="other">{t('inventory.ingredientCategories.other')}</option>
</>
) : (
<>
<option value="">{t('inventory.productCategories.select')}</option>
<option value="bread">{t('inventory.productCategories.bread')}</option>
<option value="pastry">{t('inventory.productCategories.pastry')}</option>
<option value="cake">{t('inventory.productCategories.cake')}</option>
<option value="croissants">{t('inventory.productCategories.croissants')}</option>
<option value="pastries">{t('inventory.productCategories.pastries')}</option>
<option value="cakes">{t('inventory.productCategories.cakes')}</option>
<option value="cookies">{t('inventory.productCategories.cookies')}</option>
<option value="specialty">{t('inventory.productCategories.specialty')}</option>
<option value="muffins">{t('inventory.productCategories.muffins')}</option>
<option value="sandwiches">{t('inventory.productCategories.sandwiches')}</option>
<option value="seasonal">{t('inventory.productCategories.seasonal')}</option>
<option value="beverages">{t('inventory.productCategories.beverages')}</option>
<option value="other_products">{t('inventory.productCategories.other_products')}</option>
</>
)}
</select>
@@ -310,6 +318,7 @@ const BasicInfoStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =>
const StockConfigStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
const data = dataRef?.current || {};
const { t } = useTranslation('wizards');
const { currencySymbol } = useTenantCurrency();
const [lots, setLots] = useState<any[]>(data.initialLots || []);
const handleFieldChange = (field: string, value: any) => {
@@ -381,7 +390,7 @@ const StockConfigStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =
</div>
<div>
<span className="text-[var(--text-tertiary)] block mb-1">{t('inventory.stockConfig.totalValue')}</span>
<span className="font-medium text-green-600">${totalValue.toFixed(2)}</span>
<span className="font-medium text-green-600">{currencySymbol}{totalValue.toFixed(2)}</span>
</div>
</div>
</div>
@@ -454,7 +463,7 @@ const StockConfigStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =
{/* Unit Cost */}
<div>
<label className="block text-xs font-medium text-[var(--text-secondary)] mb-1">
{t('inventory.stockConfig.unitCost')}
{t('inventory.stockConfig.unitCost')} ({currencySymbol})
</label>
<input
type="number"
@@ -513,7 +522,7 @@ const StockConfigStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) =
{lot.quantity && lot.unitCost && (
<div className="text-xs text-[var(--text-tertiary)] pt-2 border-t border-[var(--border-secondary)]">
{t('inventory.stockConfig.lotValue')} <span className="font-semibold text-green-600">
${(parseFloat(lot.quantity) * parseFloat(lot.unitCost)).toFixed(2)}
{currencySymbol}{(parseFloat(lot.quantity) * parseFloat(lot.unitCost)).toFixed(2)}
</span>
</div>
)}

View File

@@ -19,6 +19,7 @@ import { useSuppliers } from '../../../../api/hooks/suppliers';
import { useIngredients } from '../../../../api/hooks/inventory';
import { suppliersService } from '../../../../api/services/suppliers';
import { useCreatePurchaseOrder } from '../../../../api/hooks/purchase-orders';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
// Step 1: Supplier Selection
const SupplierSelectionStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
@@ -157,6 +158,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
const data = dataRef?.current || {};
const { t } = useTranslation(['wizards', 'procurement']);
const { currentTenant } = useTenant();
const { currencySymbol } = useTenantCurrency();
const [supplierProductIds, setSupplierProductIds] = useState<string[]>([]);
const [isLoadingSupplierProducts, setIsLoadingSupplierProducts] = useState(false);
@@ -338,7 +340,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
<option value="">{t('purchaseOrder.orderItems.selectIngredient')}</option>
{ingredientsData.map((product: any) => (
<option key={product.id} value={product.id}>
{product.name} - {(product.last_purchase_price || product.average_cost || 0).toFixed(2)} /{' '}
{product.name} - {currencySymbol}{(product.last_purchase_price || product.average_cost || 0).toFixed(2)} /{' '}
{product.unit_of_measure}
</option>
))}
@@ -393,7 +395,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
<div className="pt-2 border-t border-[var(--border-primary)] text-sm">
<span className="font-semibold text-[var(--text-primary)]">
{t('purchaseOrder.orderItems.subtotal')}: {item.subtotal.toFixed(2)}
{t('purchaseOrder.orderItems.subtotal')}: {currencySymbol}{item.subtotal.toFixed(2)}
</span>
</div>
</div>
@@ -405,7 +407,7 @@ const AddItemsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
<div className="p-4 bg-gradient-to-r from-[var(--color-primary)]/5 to-[var(--color-primary)]/10 rounded-lg border-2 border-[var(--color-primary)]/20">
<div className="flex justify-between items-center">
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('purchaseOrder.orderItems.total')}:</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">{calculateTotal().toFixed(2)}</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">{currencySymbol}{calculateTotal().toFixed(2)}</span>
</div>
</div>
)}
@@ -537,6 +539,7 @@ const OrderDetailsStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange }) => {
const data = dataRef?.current || {};
const { t } = useTranslation(['wizards', 'procurement']);
const { currencySymbol } = useTenantCurrency();
const calculateSubtotal = () => {
return (data.items || []).reduce((sum: number, item: any) => sum + (item.subtotal || 0), 0);
@@ -625,11 +628,11 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
<div className="flex-1">
<p className="font-medium text-[var(--text-primary)]">{item.product_name || t('purchaseOrder.review.productNoName')}</p>
<p className="text-sm text-[var(--text-secondary)]">
{item.ordered_quantity} {item.unit_of_measure} × {item.unit_price.toFixed(2)}
{item.ordered_quantity} {item.unit_of_measure} × {currencySymbol}{item.unit_price.toFixed(2)}
</p>
</div>
<div className="text-right">
<p className="font-semibold text-[var(--text-primary)]">{item.subtotal.toFixed(2)}</p>
<p className="font-semibold text-[var(--text-primary)]">{currencySymbol}{item.subtotal.toFixed(2)}</p>
</div>
</div>
))}
@@ -645,29 +648,29 @@ const ReviewSubmitStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange })
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.subtotal')}:</span>
<span className="font-medium">{calculateSubtotal().toFixed(2)}</span>
<span className="font-medium">{currencySymbol}{calculateSubtotal().toFixed(2)}</span>
</div>
{(data.tax_amount || 0) > 0 && (
<div className="flex justify-between">
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.taxes')}:</span>
<span className="font-medium">{(data.tax_amount || 0).toFixed(2)}</span>
<span className="font-medium">{currencySymbol}{(data.tax_amount || 0).toFixed(2)}</span>
</div>
)}
{(data.shipping_cost || 0) > 0 && (
<div className="flex justify-between">
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.shipping')}:</span>
<span className="font-medium">{(data.shipping_cost || 0).toFixed(2)}</span>
<span className="font-medium">{currencySymbol}{(data.shipping_cost || 0).toFixed(2)}</span>
</div>
)}
{(data.discount_amount || 0) > 0 && (
<div className="flex justify-between">
<span className="text-[var(--text-secondary)]">{t('purchaseOrder.review.discount')}:</span>
<span className="font-medium text-green-600">-{(data.discount_amount || 0).toFixed(2)}</span>
<span className="font-medium text-green-600">-{currencySymbol}{(data.discount_amount || 0).toFixed(2)}</span>
</div>
)}
<div className="pt-2 border-t-2 border-[var(--color-primary)]/30 flex justify-between">
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('purchaseOrder.review.total')}:</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">{calculateTotal().toFixed(2)}</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">{currencySymbol}{calculateTotal().toFixed(2)}</span>
</div>
</div>
</div>

View File

@@ -19,6 +19,7 @@ import { useTenant } from '../../../../stores/tenant.store';
import { salesService } from '../../../../api/services/sales';
import { inventoryService } from '../../../../api/services/inventory';
import { showToast } from '../../../../utils/toast';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
// ========================================
// STEP 1: Entry Method Selection
@@ -174,6 +175,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
const data = dataRef?.current || {};
const { t } = useTranslation('wizards');
const { currentTenant } = useTenant();
const { currencySymbol } = useTenantCurrency();
const [products, setProducts] = useState<any[]>([]);
const [loadingProducts, setLoadingProducts] = useState(true);
const [error, setError] = useState<string | null>(null);
@@ -351,7 +353,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
<option value="">{t('salesEntry.manualEntry.products.selectProduct')}</option>
{products.map((product: any) => (
<option key={product.id} value={product.id}>
{product.name} - {(product.average_cost || product.last_purchase_price || 0).toFixed(2)}
{product.name} - {currencySymbol}{(product.average_cost || product.last_purchase_price || 0).toFixed(2)}
</option>
))}
</select>
@@ -383,7 +385,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
/>
</div>
<div className="col-span-3 sm:col-span-2 text-sm font-semibold text-[var(--text-primary)]">
{item.subtotal.toFixed(2)}
{currencySymbol}{item.subtotal.toFixed(2)}
</div>
<div className="col-span-1 sm:col-span-1 flex justify-end">
<button
@@ -403,7 +405,7 @@ const ManualEntryStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onN
{(data.salesItems || []).length > 0 && (
<div className="pt-3 border-t border-[var(--border-primary)] text-right">
<span className="text-lg font-bold text-[var(--text-primary)]">
{t('salesEntry.manualEntry.products.total')} {calculateTotal().toFixed(2)}
{t('salesEntry.manualEntry.products.total')} {currencySymbol}{calculateTotal().toFixed(2)}
</span>
</div>
)}
@@ -673,6 +675,7 @@ const FileUploadStep: React.FC<WizardStepProps> = ({ dataRef, onDataChange, onNe
const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
const data = dataRef?.current || {};
const { t } = useTranslation('wizards');
const { currencySymbol } = useTenantCurrency();
const isManual = data.entryMethod === 'manual';
const isUpload = data.entryMethod === 'upload';
@@ -725,12 +728,12 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
<div className="flex-1">
<p className="font-medium text-[var(--text-primary)]">{item.product}</p>
<p className="text-sm text-[var(--text-secondary)]">
{item.quantity} × {item.unitPrice.toFixed(2)}
{item.quantity} × {currencySymbol}{item.unitPrice.toFixed(2)}
</p>
</div>
<div className="text-right">
<p className="font-semibold text-[var(--text-primary)]">
{item.subtotal.toFixed(2)}
{currencySymbol}{item.subtotal.toFixed(2)}
</p>
</div>
</div>
@@ -743,7 +746,7 @@ const ReviewStep: React.FC<WizardStepProps> = ({ dataRef }) => {
<div className="flex justify-between items-center">
<span className="text-lg font-semibold text-[var(--text-primary)]">{t('salesEntry.review.fields.total')}</span>
<span className="text-2xl font-bold text-[var(--color-primary)]">
{data.totalAmount?.toFixed(2)}
{currencySymbol}{data.totalAmount?.toFixed(2)}
</span>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import { Input } from '../Input';
import { Select } from '../Select';
import { StatusIndicatorConfig } from '../StatusCard';
import { statusColors } from '../../../styles/colors';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
// Constants to prevent re-creation on every render
const EMPTY_VALIDATION_ERRORS = {};
@@ -24,6 +25,7 @@ interface ListFieldRendererProps {
const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onChange, error }) => {
const { t } = useTranslation(['common']);
const { currencySymbol } = useTenantCurrency();
const listConfig = field.listConfig!;
const addItem = () => {
@@ -174,7 +176,7 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
{listConfig.showSubtotals && (
<div className="pt-2 border-t border-[var(--border-primary)] text-sm text-[var(--text-secondary)]">
<span className="font-medium">Subtotal: {calculateSubtotal(item).toFixed(2)}</span>
<span className="font-medium">Subtotal: {currencySymbol}{calculateSubtotal(item).toFixed(2)}</span>
</div>
)}
</div>
@@ -185,7 +187,7 @@ const ListFieldRenderer: React.FC<ListFieldRendererProps> = ({ field, value, onC
{listConfig.showSubtotals && value.length > 0 && (
<div className="pt-3 border-t border-[var(--border-primary)] text-right">
<span className="text-lg font-semibold text-[var(--text-primary)]">
Total: {calculateTotal().toFixed(2)}
Total: {currencySymbol}{calculateTotal().toFixed(2)}
</span>
</div>
)}

View File

@@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ChevronDown, ChevronUp } from 'lucide-react';
interface AdvancedOptionsSectionProps {
@@ -14,6 +15,7 @@ export const AdvancedOptionsSection: React.FC<AdvancedOptionsSectionProps> = ({
description = 'These fields are optional but help improve data management',
defaultExpanded = false,
}) => {
const { t } = useTranslation('wizards');
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
return (
@@ -26,12 +28,12 @@ export const AdvancedOptionsSection: React.FC<AdvancedOptionsSectionProps> = ({
{isExpanded ? (
<>
<ChevronUp className="w-5 h-5" />
Hide {title}
{t('common.hide')} {title}
</>
) : (
<>
<ChevronDown className="w-5 h-5" />
Show {title}
{t('common.show')} {title}
</>
)}
</button>

View File

@@ -21,6 +21,7 @@ import { renderEventTitle, renderEventMessage, renderActionLabel, renderAIReason
import { useSmartActionHandler } from '../../../utils/smartActionHandlers';
import { useAuthUser } from '../../../stores/auth.store';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
export interface NotificationPanelProps {
notifications: NotificationData[];
@@ -58,6 +59,7 @@ const EnrichedAlertItem: React.FC<{
actionHandler: any;
}> = ({ alert, isMobile, onMarkAsRead, onRemove, actionHandler }) => {
const { t } = useTranslation();
const { currencySymbol } = useTenantCurrency();
const isUnread = alert.status === 'active';
const priorityColor = getPriorityColor(alert.priority_level);
@@ -132,7 +134,7 @@ const EnrichedAlertItem: React.FC<{
{alert.business_impact?.financial_impact_eur && (
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-warning/10 text-warning text-xs">
<DollarSign className="w-3 h-3" />
<span>{alert.business_impact.financial_impact_eur.toFixed(0)} en riesgo</span>
<span>{currencySymbol}{alert.business_impact.financial_impact_eur.toFixed(0)} en riesgo</span>
</div>
)}
{alert.urgency?.hours_until_consequence && (

View File

@@ -17,9 +17,12 @@ import {
import { StatsCardProps, StatsCardVariant } from './StatsCard';
// Common formatting functions
// Note: For currency formatting with dynamic symbols, use the useTenantCurrency hook
// and createCurrencyFormatter helper function instead of formatters.currency directly
export const formatters = {
percentage: (value: string | number): string => `${value}%`,
currency: (value: string | number): string => `${parseFloat(String(value)).toFixed(2)}`,
// Currency formatter with configurable symbol (defaults to € for backwards compatibility)
currency: (value: string | number, currencySymbol: string = '€'): string => `${currencySymbol}${parseFloat(String(value)).toFixed(2)}`,
number: (value: string | number): string => parseFloat(String(value)).toLocaleString('es-ES'),
compact: (value: string | number): string => {
const num = parseFloat(String(value));
@@ -29,6 +32,10 @@ export const formatters = {
},
};
// Helper to create a currency formatter with a specific symbol
export const createCurrencyFormatter = (currencySymbol: string) =>
(value: string | number): string => `${currencySymbol}${parseFloat(String(value)).toFixed(2)}`;
// Icon mappings for common stat types
export const statIcons = {
target: Calendar,