Improve UI
This commit is contained in:
@@ -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':
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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') :
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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,
|
||||
})}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user