Add whatsapp feature
This commit is contained in:
@@ -80,7 +80,9 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl md:text-2xl font-bold mb-2" style={{ color: config.textColor }}>
|
||||
{healthStatus.headline || t(`jtbd.health_status.${status}`)}
|
||||
{typeof healthStatus.headline === 'object' && healthStatus.headline?.key
|
||||
? t(healthStatus.headline.key.replace('.', ':'), healthStatus.headline.params || {})
|
||||
: healthStatus.headline || t(`jtbd.health_status.${status}`)}
|
||||
</h2>
|
||||
|
||||
{/* Last Update */}
|
||||
@@ -117,6 +119,11 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
|
||||
const iconColor = item.actionRequired ? 'var(--color-warning-600)' : 'var(--color-success-600)';
|
||||
const bgColor = item.actionRequired ? 'var(--bg-primary)' : 'rgba(255, 255, 255, 0.5)';
|
||||
|
||||
// Translate using textKey if available, otherwise use text
|
||||
const displayText = item.textKey
|
||||
? t(item.textKey.replace('.', ':'), item.textParams || {})
|
||||
: item.text || '';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
@@ -128,7 +135,7 @@ export function HealthStatusCard({ healthStatus, loading }: HealthStatusCardProp
|
||||
className={`text-sm md:text-base ${item.actionRequired ? 'font-semibold' : ''}`}
|
||||
style={{ color: 'var(--text-primary)' }}
|
||||
>
|
||||
{item.text || ''}
|
||||
{displayText}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,19 +20,53 @@ import {
|
||||
Brain,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { OrchestrationSummary } from '../../api/hooks/newDashboard';
|
||||
import { runDailyWorkflow } from '../../api/services/orchestrator';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTenant } from '../../stores/tenant.store';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
interface OrchestrationSummaryCardProps {
|
||||
summary: OrchestrationSummary;
|
||||
loading?: boolean;
|
||||
onWorkflowComplete?: () => void;
|
||||
}
|
||||
|
||||
export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSummaryCardProps) {
|
||||
export function OrchestrationSummaryCard({ summary, loading, onWorkflowComplete }: OrchestrationSummaryCardProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const { t } = useTranslation('reasoning');
|
||||
const { currentTenant } = useTenant();
|
||||
|
||||
const handleRunPlanning = async () => {
|
||||
if (!currentTenant?.id) {
|
||||
toast.error(t('jtbd.orchestration_summary.no_tenant_error') || 'No tenant ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRunning(true);
|
||||
try {
|
||||
const result = await runDailyWorkflow(currentTenant.id);
|
||||
|
||||
if (result.success) {
|
||||
toast.success(t('jtbd.orchestration_summary.planning_started') || 'Planning started successfully');
|
||||
// Call callback to refresh the orchestration summary
|
||||
if (onWorkflowComplete) {
|
||||
onWorkflowComplete();
|
||||
}
|
||||
} else {
|
||||
toast.error(result.message || t('jtbd.orchestration_summary.planning_failed') || 'Failed to start planning');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error running daily workflow:', error);
|
||||
toast.error(t('jtbd.orchestration_summary.planning_error') || 'An error occurred while starting planning');
|
||||
} finally {
|
||||
setIsRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading || !summary) {
|
||||
return (
|
||||
@@ -57,24 +91,27 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
<div
|
||||
className="border-2 rounded-xl p-6"
|
||||
style={{
|
||||
backgroundColor: 'var(--color-info-50)',
|
||||
borderColor: 'var(--color-info-200)',
|
||||
backgroundColor: 'var(--surface-secondary)',
|
||||
borderColor: 'var(--color-info-300)',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<Bot className="w-10 h-10 flex-shrink-0" style={{ color: 'var(--color-info-600)' }} />
|
||||
<Bot className="w-10 h-10 flex-shrink-0" style={{ color: 'var(--color-info)' }} />
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-2" style={{ color: 'var(--color-info-900)' }}>
|
||||
<h3 className="text-lg font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
||||
{t('jtbd.orchestration_summary.ready_to_plan')}
|
||||
</h3>
|
||||
<p className="mb-4" style={{ color: 'var(--color-info-700)' }}>{summary.message || ''}</p>
|
||||
<p className="mb-4" style={{ color: 'var(--text-secondary)' }}>{summary.message || ''}</p>
|
||||
<button
|
||||
className="px-4 py-2 rounded-lg font-semibold transition-colors duration-200"
|
||||
onClick={handleRunPlanning}
|
||||
disabled={isRunning}
|
||||
className="px-4 py-2 rounded-lg font-semibold transition-colors duration-200 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed hover:opacity-90"
|
||||
style={{
|
||||
backgroundColor: 'var(--color-info-600)',
|
||||
color: 'var(--text-inverse)',
|
||||
backgroundColor: 'var(--color-info)',
|
||||
color: 'var(--bg-primary)',
|
||||
}}
|
||||
>
|
||||
{isRunning && <Loader2 className="w-4 h-4 animate-spin" />}
|
||||
{t('jtbd.orchestration_summary.run_planning')}
|
||||
</button>
|
||||
</div>
|
||||
@@ -91,14 +128,14 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
<div
|
||||
className="rounded-xl shadow-md p-6 border"
|
||||
style={{
|
||||
background: 'linear-gradient(to bottom right, var(--color-primary-50), var(--color-info-50))',
|
||||
borderColor: 'var(--color-primary-100)',
|
||||
background: 'linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%)',
|
||||
borderColor: 'var(--border-primary)',
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start gap-4 mb-6">
|
||||
<div className="p-3 rounded-full" style={{ backgroundColor: 'var(--color-primary-100)' }}>
|
||||
<Bot className="w-8 h-8" style={{ color: 'var(--color-primary-600)' }} />
|
||||
<div className="p-3 rounded-full" style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
||||
<Bot className="w-8 h-8" style={{ color: 'var(--color-primary)' }} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h2 className="text-2xl font-bold mb-1" style={{ color: 'var(--text-primary)' }}>
|
||||
@@ -210,9 +247,9 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
)}
|
||||
|
||||
{/* Reasoning Inputs (How decisions were made) */}
|
||||
<div className="rounded-lg p-4" style={{ backgroundColor: 'rgba(255, 255, 255, 0.6)' }}>
|
||||
<div className="rounded-lg p-4" style={{ backgroundColor: 'var(--surface-secondary)', border: '1px solid var(--border-primary)' }}>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Brain className="w-5 h-5" style={{ color: 'var(--color-primary-600)' }} />
|
||||
<Brain className="w-5 h-5" style={{ color: 'var(--color-primary)' }} />
|
||||
<h3 className="font-bold" style={{ color: 'var(--text-primary)' }}>{t('jtbd.orchestration_summary.based_on')}</h3>
|
||||
</div>
|
||||
|
||||
@@ -260,13 +297,13 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
<div
|
||||
className="mt-4 p-4 border rounded-lg"
|
||||
style={{
|
||||
backgroundColor: 'var(--color-warning-50)',
|
||||
borderColor: 'var(--color-warning-200)',
|
||||
backgroundColor: 'var(--bg-tertiary)',
|
||||
borderColor: 'var(--color-warning)',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="w-5 h-5" style={{ color: 'var(--color-warning-600)' }} />
|
||||
<p className="text-sm font-medium" style={{ color: 'var(--color-warning-900)' }}>
|
||||
<FileText className="w-5 h-5" style={{ color: 'var(--color-warning)' }} />
|
||||
<p className="text-sm font-medium" style={{ color: 'var(--text-primary)' }}>
|
||||
{t('jtbd.orchestration_summary.actions_required', {
|
||||
count: summary.userActionsRequired,
|
||||
})}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { EditViewModal, StatusModalSection } from '@/components/ui/EditViewModal/EditViewModal';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Tooltip } from '@/components/ui/Tooltip';
|
||||
import { TrainedModelResponse, TrainingMetrics } from '@/types/training';
|
||||
import { Activity, TrendingUp, Calendar, Settings, Lightbulb, AlertTriangle, CheckCircle, Target } from 'lucide-react';
|
||||
import { Activity, TrendingUp, Calendar, Settings, Lightbulb, AlertTriangle, CheckCircle, Target, MapPin } from 'lucide-react';
|
||||
import { POI_CATEGORY_METADATA } from '@/types/poi';
|
||||
|
||||
interface ModelDetailsModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -88,9 +89,65 @@ const FeatureTag: React.FC<{ feature: string }> = ({ feature }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ModelDetailsModal: React.FC<ModelDetailsModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
// POI Category Card component
|
||||
const POICategoryCard: React.FC<{
|
||||
category: string;
|
||||
featureCount: number;
|
||||
metrics: Set<string>;
|
||||
}> = ({ category, featureCount, metrics }) => {
|
||||
const metadata = POI_CATEGORY_METADATA[category];
|
||||
|
||||
if (!metadata) return null;
|
||||
|
||||
const metricsList = Array.from(metrics);
|
||||
const hasProximity = metricsList.some(m => m.includes('proximity'));
|
||||
const hasDistance = metricsList.some(m => m.includes('distance'));
|
||||
const hasCounts = metricsList.some(m => m.includes('count'));
|
||||
|
||||
const metricsDescription = [
|
||||
hasProximity && 'proximity scores',
|
||||
hasDistance && 'distances',
|
||||
hasCounts && 'location counts'
|
||||
].filter(Boolean).join(', ');
|
||||
|
||||
return (
|
||||
<Tooltip content={`${metadata.description}. Uses: ${metricsDescription}`}>
|
||||
<div
|
||||
className="flex items-center gap-3 p-3 rounded-lg border transition-all hover:shadow-sm"
|
||||
style={{
|
||||
backgroundColor: 'var(--bg-secondary)',
|
||||
borderColor: 'var(--border-color)'
|
||||
}}
|
||||
>
|
||||
<span style={{ fontSize: '28px' }} aria-hidden="true">
|
||||
{metadata.icon}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm text-[var(--text-primary)]">
|
||||
{metadata.displayName}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--text-secondary)] truncate">
|
||||
{featureCount} feature{featureCount !== 1 ? 's' : ''} • {metricsList.length} metric{metricsList.length !== 1 ? 's' : ''}
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
style={{
|
||||
backgroundColor: `${metadata.color}20`,
|
||||
color: metadata.color
|
||||
}}
|
||||
>
|
||||
Active
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const ModelDetailsModal: React.FC<ModelDetailsModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
model,
|
||||
onRetrain,
|
||||
onViewPredictions
|
||||
@@ -116,6 +173,40 @@ const ModelDetailsModal: React.FC<ModelDetailsModalProps> = ({
|
||||
const performanceColor = getPerformanceColor(accuracy);
|
||||
const performanceMessage = getPerformanceMessage(accuracy);
|
||||
|
||||
// Parse POI features from model features array
|
||||
const poiFeatureAnalysis = useMemo(() => {
|
||||
const features = ((model as any).features || []) as string[];
|
||||
const poiFeatures = features.filter(f => f.startsWith('poi_'));
|
||||
|
||||
// Group by category
|
||||
const byCategory: Record<string, string[]> = {};
|
||||
const categoryMetrics: Record<string, Set<string>> = {};
|
||||
|
||||
poiFeatures.forEach(feature => {
|
||||
const parts = feature.split('_');
|
||||
if (parts.length >= 3 && parts[0] === 'poi') {
|
||||
const category = parts[1]; // e.g., "schools"
|
||||
const metric = parts.slice(2).join('_'); // e.g., "proximity_score" or "count_0_100m"
|
||||
|
||||
if (!byCategory[category]) {
|
||||
byCategory[category] = [];
|
||||
categoryMetrics[category] = new Set();
|
||||
}
|
||||
byCategory[category].push(feature);
|
||||
categoryMetrics[category].add(metric);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
allPOIFeatures: poiFeatures,
|
||||
byCategory,
|
||||
categoryMetrics,
|
||||
categoryCount: Object.keys(byCategory).length,
|
||||
hasAnyPOI: poiFeatures.length > 0,
|
||||
totalPOIFeatures: poiFeatures.length
|
||||
};
|
||||
}, [(model as any).features]);
|
||||
|
||||
// Prepare sections for StatusModal
|
||||
const sections: StatusModalSection[] = [
|
||||
{
|
||||
@@ -299,6 +390,77 @@ const ModelDetailsModal: React.FC<ModelDetailsModalProps> = ({
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Factores de Ubicación (POI)",
|
||||
icon: MapPin,
|
||||
fields: [
|
||||
{
|
||||
label: "Contexto de la Ubicación",
|
||||
value: (() => {
|
||||
if (!poiFeatureAnalysis.hasAnyPOI) {
|
||||
return (
|
||||
<div className="text-sm text-[var(--text-secondary)] italic bg-[var(--bg-secondary)] rounded-md p-4 border border-dashed border-[var(--border-color)]">
|
||||
No se detectaron factores de ubicación (POI) en este modelo.
|
||||
El modelo se basa en datos de ventas, temporales y climáticos.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const categories = Object.keys(poiFeatureAnalysis.byCategory).sort();
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
El modelo utiliza <strong>{poiFeatureAnalysis.totalPOIFeatures} características de ubicación</strong> de{' '}
|
||||
<strong>{poiFeatureAnalysis.categoryCount} categorías POI</strong> para mejorar las predicciones basándose en el entorno de tu panadería.
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
{categories.map(category => (
|
||||
<POICategoryCard
|
||||
key={category}
|
||||
category={category}
|
||||
featureCount={poiFeatureAnalysis.byCategory[category].length}
|
||||
metrics={poiFeatureAnalysis.categoryMetrics[category]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[var(--text-secondary)] bg-[var(--bg-secondary)] rounded-md p-3 border-l-4 border-[var(--color-success)]">
|
||||
<strong>📍 Factores POI:</strong> Estos factores de ubicación ayudan al modelo a entender cómo el entorno de tu panadería (escuelas, oficinas, transporte, etc.) afecta tus ventas. Cada categoría contribuye con múltiples métricas como proximidad, distancia y conteos de ubicaciones.
|
||||
</div>
|
||||
|
||||
{/* Advanced debugging info - expandable */}
|
||||
<details className="text-xs">
|
||||
<summary className="cursor-pointer font-medium text-[var(--text-primary)] hover:text-[var(--color-primary)] transition-colors py-2">
|
||||
🔍 Ver detalles técnicos de POI
|
||||
</summary>
|
||||
<div className="mt-2 space-y-2 pl-4 border-l-2 border-[var(--border-color)]">
|
||||
{categories.map(category => {
|
||||
const features = poiFeatureAnalysis.byCategory[category];
|
||||
const metadata = POI_CATEGORY_METADATA[category];
|
||||
return (
|
||||
<div key={category} className="space-y-1">
|
||||
<div className="font-medium text-[var(--text-primary)]">
|
||||
{metadata?.icon} {metadata?.displayName || category}
|
||||
</div>
|
||||
<div className="text-[var(--text-secondary)] space-y-0.5 ml-2">
|
||||
{features.map(feature => (
|
||||
<div key={feature} className="font-mono text-xs">• {feature}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
})(),
|
||||
span: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Detalles Técnicos",
|
||||
icon: Calendar,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../../ui/Button';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { ChartBar, ShoppingCart, Users, TrendingUp, Zap, CheckCircle2 } from 'lucide-react';
|
||||
import { BarChart, ShoppingCart, Users, TrendingUp, Zap, CheckCircle2 } from 'lucide-react';
|
||||
|
||||
interface CompletionStepProps {
|
||||
onNext: () => void;
|
||||
@@ -148,7 +148,7 @@ export const CompletionStep: React.FC<CompletionStepProps> = ({
|
||||
onClick={() => navigate('/app/dashboard')}
|
||||
className="p-4 bg-[var(--bg-secondary)] hover:bg-[var(--bg-tertiary)] border border-[var(--border-secondary)] rounded-lg transition-all hover:shadow-lg hover:scale-105 text-left group"
|
||||
>
|
||||
<ChartBar className="w-8 h-8 text-[var(--color-primary)] mb-2 group-hover:scale-110 transition-transform" />
|
||||
<BarChart className="w-8 h-8 text-[var(--color-primary)] mb-2 group-hover:scale-110 transition-transform" />
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-1">
|
||||
{t('onboarding:completion.quick.analytics', 'Analíticas')}
|
||||
</h4>
|
||||
|
||||
@@ -97,8 +97,8 @@ export const InitialStockEntryStep: React.FC<InitialStockEntryStepProps> = ({
|
||||
tenantId,
|
||||
stockData: {
|
||||
ingredient_id: product.id,
|
||||
unit_price: 0, // Default price, can be updated later
|
||||
notes: `Initial stock entry from onboarding`
|
||||
current_quantity: product.initialStock!, // The actual stock quantity
|
||||
unit_cost: 0, // Default cost, can be updated later
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -308,8 +308,9 @@ export const InventoryReviewStep: React.FC<InventoryReviewStepProps> = ({
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(createPromises);
|
||||
const createdIngredients = await Promise.all(createPromises);
|
||||
console.log('✅ Inventory items created successfully');
|
||||
console.log('📋 Created ingredient IDs:', createdIngredients.map(ing => ({ name: ing.name, id: ing.id })));
|
||||
|
||||
// STEP 2: Import sales data (only if file was uploaded)
|
||||
// Now that inventory exists, sales records can reference the inventory IDs
|
||||
@@ -331,10 +332,21 @@ export const InventoryReviewStep: React.FC<InventoryReviewStepProps> = ({
|
||||
}
|
||||
|
||||
// Complete the step with metadata and inventory items
|
||||
// Map created ingredients to include their real UUIDs
|
||||
const itemsWithRealIds = createdIngredients.map(ingredient => ({
|
||||
id: ingredient.id, // Real UUID from the API
|
||||
name: ingredient.name,
|
||||
product_type: ingredient.product_type,
|
||||
category: ingredient.category,
|
||||
unit_of_measure: ingredient.unit_of_measure,
|
||||
}));
|
||||
|
||||
console.log('📦 Passing items with real IDs to next step:', itemsWithRealIds);
|
||||
|
||||
onComplete({
|
||||
inventoryItemsCreated: inventoryItems.length,
|
||||
inventoryItemsCreated: createdIngredients.length,
|
||||
salesDataImported: salesImported,
|
||||
inventoryItems: inventoryItems, // Pass the created items to the next step
|
||||
inventoryItems: itemsWithRealIds, // Pass the created items with real UUIDs to the next step
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating inventory items:', error);
|
||||
|
||||
@@ -59,6 +59,8 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
|
||||
// Get wizard steps based on selected item type
|
||||
// CRITICAL: Memoize the steps to prevent component recreation on every render
|
||||
// Without this, every keystroke causes the component to unmount/remount, losing focus
|
||||
// IMPORTANT: For dynamic wizards (like sales-entry), we need to include the entryMethod
|
||||
// in the dependency array so steps update when the user selects manual vs upload
|
||||
const wizardSteps = useMemo((): WizardStep[] => {
|
||||
if (!selectedItemType) {
|
||||
// Step 0: Item Type Selection
|
||||
@@ -67,7 +69,7 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
|
||||
id: 'item-type-selection',
|
||||
title: 'Seleccionar tipo',
|
||||
description: 'Elige qué deseas agregar',
|
||||
component: (props) => (
|
||||
component: () => (
|
||||
<ItemTypeSelector onSelect={handleItemTypeSelect} />
|
||||
),
|
||||
},
|
||||
@@ -97,7 +99,7 @@ export const UnifiedAddWizard: React.FC<UnifiedAddWizardProps> = ({
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}, [selectedItemType, handleItemTypeSelect]); // Only recreate when item type changes, NOT when wizardData changes
|
||||
}, [selectedItemType, handleItemTypeSelect, wizardData.entryMethod]); // Include only critical fields for dynamic step generation
|
||||
|
||||
// Get wizard title based on selected item type
|
||||
const getWizardTitle = (): string => {
|
||||
|
||||
@@ -109,7 +109,7 @@ export const WizardModal: React.FC<WizardModalProps> = ({
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 z-50 animate-fadeIn"
|
||||
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 animate-fadeIn"
|
||||
onClick={handleClose}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user