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