Improve UI

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

View File

@@ -41,6 +41,7 @@ import { apiClient } from '../../api/client/apiClient';
import { useEnterprise } from '../../contexts/EnterpriseContext';
import { useTenant } from '../../stores/tenant.store';
import { useSSEEvents } from '../../hooks/useSSE';
import { useTenantCurrency } from '../../hooks/useTenantCurrency';
import { useQueryClient } from '@tanstack/react-query';
// Components for enterprise dashboard
@@ -64,6 +65,7 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
const { t } = useTranslation('dashboard');
const { state: enterpriseState, drillDownToOutlet, returnToNetworkView, enterNetworkView } = useEnterprise();
const { switchTenant } = useTenant();
const { currencySymbol } = useTenantCurrency();
const [selectedMetric, setSelectedMetric] = useState('sales');
const [selectedPeriod, setSelectedPeriod] = useState(30);
@@ -315,7 +317,7 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
style={{ borderColor: 'var(--border-primary)' }}>
<div>
<span className="text-[var(--color-info)]">Network Average Sales:</span>
<span className="ml-2 font-semibold text-[var(--text-primary)]">{enterpriseState.networkMetrics.averageSales.toLocaleString()}</span>
<span className="ml-2 font-semibold text-[var(--text-primary)]">{currencySymbol}{enterpriseState.networkMetrics.averageSales.toLocaleString()}</span>
</div>
<div>
<span className="text-[var(--color-info)]">Total Outlets:</span>
@@ -323,7 +325,7 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
</div>
<div>
<span className="text-[var(--color-info)]">Network Total:</span>
<span className="ml-2 font-semibold text-[var(--text-primary)]">{enterpriseState.networkMetrics.totalSales.toLocaleString()}</span>
<span className="ml-2 font-semibold text-[var(--text-primary)]">{currencySymbol}{enterpriseState.networkMetrics.totalSales.toLocaleString()}</span>
</div>
</div>
)}

View File

@@ -25,11 +25,13 @@ import { useSubscription } from '../../../api/hooks/subscription';
import { useCurrentTenant } from '../../../stores/tenant.store';
import { useProcurementDashboard, useProcurementTrends } from '../../../api/hooks/procurement';
import { formatters } from '../../../components/ui/Stats/StatsPresets';
import { useTenantCurrency } from '../../../hooks/useTenantCurrency';
const ProcurementAnalyticsPage: React.FC = () => {
const { canAccessAnalytics, subscriptionInfo } = useSubscription();
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const [activeTab, setActiveTab] = useState('overview');
@@ -199,7 +201,7 @@ const ProcurementAnalyticsPage: React.FC = () => {
{plan.total_requirements}
</td>
<td className="py-4 px-6 text-sm text-right font-bold text-[var(--text-primary)]">
{formatters.currency(plan.total_estimated_cost)}
{currencySymbol}{formatters.currency(plan.total_estimated_cost, '')}
</td>
</tr>
))}
@@ -378,7 +380,7 @@ const ProcurementAnalyticsPage: React.FC = () => {
<div>
<span className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide">Costo Total Estimado</span>
<div className="text-3xl font-bold text-[var(--text-primary)] mt-1">
{formatters.currency(dashboard?.summary?.total_estimated_cost || 0)}
{currencySymbol}{formatters.currency(dashboard?.summary?.total_estimated_cost || 0, '')}
</div>
</div>
<DollarSign className="h-12 w-12 text-[var(--color-info)] opacity-20" />
@@ -387,7 +389,7 @@ const ProcurementAnalyticsPage: React.FC = () => {
<div>
<span className="text-xs text-[var(--text-tertiary)] uppercase tracking-wide">Costo Total Aprobado</span>
<div className="text-3xl font-bold text-[var(--text-primary)] mt-1">
{formatters.currency(dashboard?.summary?.total_approved_cost || 0)}
{currencySymbol}{formatters.currency(dashboard?.summary?.total_approved_cost || 0, '')}
</div>
</div>
<DollarSign className="h-12 w-12 text-[var(--color-success)] opacity-20" />
@@ -400,7 +402,7 @@ const ProcurementAnalyticsPage: React.FC = () => {
? 'text-[var(--color-error)]'
: 'text-[var(--color-success)]'
}`}>
{(dashboard?.summary?.cost_variance || 0) > 0 ? '+' : ''}{formatters.currency(dashboard?.summary?.cost_variance || 0)}
{(dashboard?.summary?.cost_variance || 0) > 0 ? '+' : ''}{currencySymbol}{formatters.currency(dashboard?.summary?.cost_variance || 0, '')}
</div>
</div>
<TrendingUp className={`h-12 w-12 opacity-20 ${
@@ -419,7 +421,7 @@ const ProcurementAnalyticsPage: React.FC = () => {
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-[var(--text-primary)]">{category.name}</span>
<span className="text-sm font-bold text-[var(--text-primary)]">
{formatters.currency(category.amount)}
{currencySymbol}{formatters.currency(category.amount, '')}
</span>
</div>
<div className="relative">

View File

@@ -34,6 +34,7 @@ import { Badge, Card } from '../../../../components/ui';
import { AnalyticsPageLayout, AnalyticsCard } from '../../../../components/analytics';
import { useSubscription } from '../../../../api/hooks/subscription';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
import {
useCycleTimeMetrics,
useProcessEfficiencyScore,
@@ -46,18 +47,19 @@ import {
} from '../../../../api/hooks/performance';
import { TimePeriod } from '../../../../api/types/performance';
// Formatters for StatsGrid
// Formatters for StatsGrid - Note: currency uses dynamic symbol from hook in the component
const formatters = {
number: (value: number) => value.toFixed(0),
percentage: (value: number) => `${value.toFixed(1)}%`,
hours: (value: number) => `${value.toFixed(1)}h`,
currency: (value: number) => `${value.toLocaleString('es-ES', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`,
currency: (value: number, currencySymbol: string = '€') => `${currencySymbol}${value.toLocaleString('es-ES', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`,
};
const PerformanceAnalyticsPage: React.FC = () => {
const { canAccessAnalytics, subscriptionInfo } = useSubscription();
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const [selectedPeriod, setSelectedPeriod] = useState<TimePeriod>('week');
const [activeTab, setActiveTab] = useState('overview');
@@ -515,13 +517,13 @@ const PerformanceAnalyticsPage: React.FC = () => {
<div className="text-center p-6 bg-[var(--bg-secondary)] rounded-lg">
<p className="text-sm text-[var(--text-secondary)] mb-2">Ingresos Totales</p>
<p className="text-3xl font-bold text-green-600">
{costRevenue.total_revenue.toLocaleString('es-ES')}
{currencySymbol}{costRevenue.total_revenue.toLocaleString('es-ES')}
</p>
</div>
<div className="text-center p-6 bg-[var(--bg-secondary)] rounded-lg">
<p className="text-sm text-[var(--text-secondary)] mb-2">Costos Estimados</p>
<p className="text-3xl font-bold text-red-600">
{costRevenue.estimated_costs.toLocaleString('es-ES')}
{currencySymbol}{costRevenue.estimated_costs.toLocaleString('es-ES')}
</p>
</div>
</div>

View File

@@ -18,11 +18,13 @@ import { LoadingSpinner } from '../../../../components/ui';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { useSustainabilityMetrics } from '../../../../api/hooks/sustainability';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
const SustainabilityPage: React.FC = () => {
const { t } = useTranslation(['sustainability', 'common']);
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
// Date range state (default to last 30 days)
const [dateRange, setDateRange] = useState<{ start?: string; end?: string }>({});
@@ -143,7 +145,7 @@ const SustainabilityPage: React.FC = () => {
},
{
title: t('sustainability:stats.monthly_savings', 'Monthly Savings'),
value: `${metrics.financial_impact.potential_monthly_savings.toFixed(0)}`,
value: `${currencySymbol}${metrics.financial_impact.potential_monthly_savings.toFixed(0)}`,
icon: Euro,
variant: 'success' as const,
subtitle: t('sustainability:stats.from_waste_reduction', 'From waste reduction')
@@ -512,7 +514,7 @@ const SustainabilityPage: React.FC = () => {
</div>
{program.funding_eur && program.funding_eur > 0 && (
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('sustainability:grant.funding', 'Financiación')}: {program.funding_eur.toLocaleString()}
{t('sustainability:grant.funding', 'Financiación')}: {currencySymbol}{program.funding_eur.toLocaleString()}
</p>
)}
</div>
@@ -569,10 +571,10 @@ const SustainabilityPage: React.FC = () => {
{t('sustainability:financial.waste_cost', 'Coste de Residuos')}
</p>
<p className="text-2xl font-bold text-red-600">
{metrics.financial_impact.waste_cost_eur.toFixed(2)}
{currencySymbol}{metrics.financial_impact.waste_cost_eur.toFixed(2)}
</p>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{metrics.financial_impact.cost_per_kg.toFixed(2)}/kg
{currencySymbol}{metrics.financial_impact.cost_per_kg.toFixed(2)}/kg
</p>
</div>
@@ -581,7 +583,7 @@ const SustainabilityPage: React.FC = () => {
{t('sustainability:financial.monthly_savings', 'Ahorro Mensual')}
</p>
<p className="text-2xl font-bold text-green-600 dark:text-green-400">
{metrics.financial_impact.potential_monthly_savings.toFixed(2)}
{currencySymbol}{metrics.financial_impact.potential_monthly_savings.toFixed(2)}
</p>
<p className="text-xs text-green-600/80 dark:text-green-400/80 mt-1">
{t('sustainability:financial.from_reduction', 'Por reducción')}
@@ -593,7 +595,7 @@ const SustainabilityPage: React.FC = () => {
{t('sustainability:financial.annual_projection', 'Proyección Anual')}
</p>
<p className="text-2xl font-bold text-[var(--text-primary)]">
{metrics.financial_impact.annual_projection.toFixed(2)}
{currencySymbol}{metrics.financial_impact.annual_projection.toFixed(2)}
</p>
<p className="text-xs text-[var(--text-secondary)] mt-1">
{t('sustainability:financial.estimated', 'Estimado')}
@@ -605,7 +607,7 @@ const SustainabilityPage: React.FC = () => {
{t('sustainability:financial.roi', 'ROI de IA')}
</p>
<p className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{(metrics.avoided_waste.waste_avoided_kg * metrics.financial_impact.cost_per_kg).toFixed(2)}
{currencySymbol}{(metrics.avoided_waste.waste_avoided_kg * metrics.financial_impact.cost_per_kg).toFixed(2)}
</p>
<p className="text-xs text-blue-600/80 dark:text-blue-400/80 mt-1">
{t('sustainability:financial.ai_savings', 'Ahorrado por IA')}

View File

@@ -26,6 +26,7 @@ import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useAuthUser } from '../../../../stores/auth.store';
import { OrderFormModal } from '../../../../components/domain/orders';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
const OrdersPage: React.FC = () => {
const [activeTab, setActiveTab] = useState<'orders' | 'customers'>('orders');
@@ -44,6 +45,7 @@ const OrdersPage: React.FC = () => {
const user = useAuthUser();
const tenantId = currentTenant?.id || user?.tenant_id || '';
const { t } = useTranslation(['orders', 'common']);
const { currencySymbol } = useTenantCurrency();
// API hooks for orders
const {
@@ -374,7 +376,7 @@ const OrdersPage: React.FC = () => {
primaryValueLabel="artículos"
secondaryInfo={{
label: 'Total',
value: `${formatters.compact(order.total_amount)}`
value: `${currencySymbol}${formatters.compact(order.total_amount)}`
}}
metadata={[
`Pedido: ${new Date(order.order_date).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit' })}`,
@@ -422,7 +424,7 @@ const OrdersPage: React.FC = () => {
primaryValueLabel="pedidos"
secondaryInfo={{
label: 'Total',
value: `${formatters.compact(customer.total_spent || 0)}`
value: `${currencySymbol}${formatters.compact(customer.total_spent || 0)}`
}}
metadata={[
`${customer.customer_code}`,

View File

@@ -6,6 +6,7 @@ import { LoadingSpinner } from '../../../../components/ui';
import { formatters } from '../../../../components/ui/Stats/StatsPresets';
import { useIngredients } from '../../../../api/hooks/inventory';
import { useTenantId } from '../../../../hooks/useTenantId';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
import { ProductType, ProductCategory, IngredientResponse } from '../../../../api/types/inventory';
import { showToast } from '../../../../utils/toast';
import { usePOSConfigurationData, usePOSConfigurationManager, usePOSTransactions, usePOSTransactionsDashboard, usePOSTransaction } from '../../../../api/hooks/pos';
@@ -548,7 +549,7 @@ const POSPage: React.FC = () => {
const [testingConnection, setTestingConnection] = useState<string | null>(null);
const tenantId = useTenantId();
const { currencySymbol } = useTenantCurrency();
// POS Configuration hooks
const posData = usePOSConfigurationData(tenantId);
@@ -780,7 +781,7 @@ const POSPage: React.FC = () => {
}
setCart([]);
showToast.success(`Venta procesada exitosamente: ${total.toFixed(2)}`);
showToast.success(`Venta procesada exitosamente: ${currencySymbol}${total.toFixed(2)}`);
} catch (error: any) {
console.error('Error processing payment:', error);
showToast.error(error.response?.data?.detail || 'Error al procesar la venta');

View File

@@ -18,8 +18,11 @@ import type { PurchaseOrderStatus, PurchaseOrderPriority, PurchaseOrderDetail }
import { useTenantStore } from '../../../../stores/tenant.store';
import { useUserById } from '../../../../api/hooks/user';
import { showToast } from '../../../../utils/toast';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
const ProcurementPage: React.FC = () => {
const { currencySymbol } = useTenantCurrency();
// State
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<PurchaseOrderStatus | ''>('');
@@ -500,7 +503,7 @@ const ProcurementPage: React.FC = () => {
title={String(po.po_number || 'Sin número')}
subtitle={String(po.supplier_name || po.supplier?.name || 'Proveedor desconocido')}
statusIndicator={statusConfig}
primaryValue={`${totalAmount}`}
primaryValue={`${currencySymbol}${totalAmount}`}
primaryValueLabel="Total"
metadata={[
`Prioridad: ${priorityText}`,

View File

@@ -7,6 +7,7 @@ import { PageHeader } from '../../../../components/layout';
import { useRecipes, useCreateRecipe, useUpdateRecipe, useDeleteRecipe, useArchiveRecipe } from '../../../../api/hooks/recipes';
import { recipesService } from '../../../../api/services/recipes';
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
import type { RecipeResponse, RecipeCreate } from '../../../../api/types/recipes';
import { MeasurementUnit } from '../../../../api/types/recipes';
import { useIngredients } from '../../../../api/hooks/inventory';
@@ -273,6 +274,7 @@ const RecipesPage: React.FC = () => {
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
const { currencySymbol } = useTenantCurrency();
const queryClient = useQueryClient();
// Mutations
@@ -1520,7 +1522,7 @@ const RecipesPage: React.FC = () => {
primaryValueLabel="ingredientes"
secondaryInfo={{
label: 'Margen',
value: `${formatters.compact(price - cost)}`
value: `${currencySymbol}${formatters.compact(price - cost)}`
}}
progress={{
label: 'Margen de beneficio',

View File

@@ -8,6 +8,7 @@ import { useSuppliers, useSupplierStatistics, useCreateSupplier, useUpdateSuppli
import { useCurrentTenant } from '../../../../stores/tenant.store';
import { useAuthUser } from '../../../../stores/auth.store';
import { useTranslation } from 'react-i18next';
import { useTenantCurrency } from '../../../../hooks/useTenantCurrency';
import { statusColors } from '../../../../styles/colors';
import { DeleteSupplierModal, SupplierPriceListViewModal, PriceListModal } from '../../../../components/domain/suppliers';
import { useQueryClient } from '@tanstack/react-query';
@@ -35,6 +36,7 @@ const SuppliersPage: React.FC = () => {
const currentTenant = useCurrentTenant();
const user = useAuthUser();
const tenantId = currentTenant?.id || user?.tenant_id || '';
const { currencySymbol } = useTenantCurrency();
// API hooks
const {
@@ -299,7 +301,7 @@ const SuppliersPage: React.FC = () => {
primaryValueLabel="días entrega"
secondaryInfo={{
label: 'Pedido Min.',
value: `${formatters.compact(supplier.minimum_order_amount || 0)}`
value: `${currencySymbol}${formatters.compact(supplier.minimum_order_amount || 0)}`
}}
metadata={[
supplier.contact_person || 'Sin contacto',

View File

@@ -124,9 +124,9 @@ const BakerySettingsPage: React.FC = () => {
postalCode: currentTenant.postal_code || '',
country: currentTenant.country || '',
taxId: '',
currency: 'EUR',
timezone: 'Europe/Madrid',
language: 'es'
currency: currentTenant.currency || 'EUR',
timezone: currentTenant.timezone || 'Europe/Madrid',
language: currentTenant.language || 'es'
});
setHasUnsavedChanges(false);
}
@@ -203,7 +203,11 @@ const BakerySettingsPage: React.FC = () => {
address: config.address,
city: config.city,
postal_code: config.postalCode,
country: config.country
country: config.country,
// Regional/Localization settings
currency: config.currency,
timezone: config.timezone,
language: config.language
};
const updatedTenant = await updateTenantMutation.mutateAsync({
@@ -308,9 +312,9 @@ const BakerySettingsPage: React.FC = () => {
postalCode: currentTenant.postal_code || '',
country: currentTenant.country || '',
taxId: '',
currency: 'EUR',
timezone: 'Europe/Madrid',
language: 'es'
currency: currentTenant.currency || 'EUR',
timezone: currentTenant.timezone || 'Europe/Madrid',
language: currentTenant.language || 'es'
});
}
if (settings) {