Refactor admin panel menu structure

This commit is contained in:
gpt-engineer-app[bot]
2025-10-11 15:41:15 +00:00
parent 447eadd88c
commit 60b7e9a73d
9 changed files with 567 additions and 4 deletions

View File

@@ -35,14 +35,20 @@ import Invoices from "./pages/dashboard/Invoices";
import InvoiceDetail from "./pages/dashboard/InvoiceDetail";
import HotelManagement from "./pages/dashboard/HotelManagement";
import RestaurantPOS from "./pages/dashboard/RestaurantPOS";
import Personalization from "./pages/dashboard/Personalization";
import Security from "./pages/dashboard/Security";
import VehicleManagement from "./pages/dashboard/VehicleManagement";
import Sustainability from "./pages/dashboard/Sustainability";
import Establishments from "./pages/dashboard/Establishments";
import Analytics from "./pages/dashboard/Analytics";
import SearchPlaces from "./pages/dashboard/SearchPlaces";
import SearchEstablishments from "./pages/dashboard/SearchEstablishments";
// Config pages
import APIs from "./pages/dashboard/config/APIs";
import Payments from "./pages/dashboard/config/Payments";
import Parameters from "./pages/dashboard/config/Parameters";
import Integrations from "./pages/dashboard/config/Integrations";
import Audit from "./pages/dashboard/config/Audit";
import SecurityCenter from "./pages/dashboard/config/SecurityCenter";
import PersonalizationPage from "./pages/dashboard/config/PersonalizationPage";
// Commerce pages (for retail stores)
import CommerceStore from "./pages/dashboard/commerce/Store";
import CommercePOS from "./pages/dashboard/commerce/POSTerminal";
@@ -507,6 +513,63 @@ const AppRouter = () => (
</ProtectedRoute>
} />
{/* Config Routes */}
<Route path="/dashboard/config/apis" element={
<ProtectedRoute>
<DashboardLayout>
<APIs />
</DashboardLayout>
</ProtectedRoute>
} />
<Route path="/dashboard/config/payments" element={
<ProtectedRoute>
<DashboardLayout>
<Payments />
</DashboardLayout>
</ProtectedRoute>
} />
<Route path="/dashboard/config/parameters" element={
<ProtectedRoute>
<DashboardLayout>
<Parameters />
</DashboardLayout>
</ProtectedRoute>
} />
<Route path="/dashboard/config/integrations" element={
<ProtectedRoute>
<DashboardLayout>
<Integrations />
</DashboardLayout>
</ProtectedRoute>
} />
<Route path="/dashboard/config/audit" element={
<ProtectedRoute>
<DashboardLayout>
<Audit />
</DashboardLayout>
</ProtectedRoute>
} />
<Route path="/dashboard/config/security" element={
<ProtectedRoute>
<DashboardLayout>
<SecurityCenter />
</DashboardLayout>
</ProtectedRoute>
} />
<Route path="/dashboard/config/personalization" element={
<ProtectedRoute>
<DashboardLayout>
<PersonalizationPage />
</DashboardLayout>
</ProtectedRoute>
} />
{/* Catch-all route */}
<Route path="*" element={<NotFound />} />
</Routes>

View File

@@ -56,7 +56,8 @@ import {
Radio,
Sparkles,
Leaf,
Store
Store,
Server
} from 'lucide-react';
const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
@@ -115,7 +116,20 @@ const DashboardLayout = ({ children }: { children: React.ReactNode }) => {
},
{ icon: AlertTriangle, label: 'Emergencias', path: '/dashboard/admin?tab=emergency' },
{ icon: MessageSquare, label: 'Soporte', path: '/dashboard/admin?tab=support' },
{ icon: Settings, label: 'Configuración', path: '/dashboard/admin?tab=config' },
{
icon: Settings,
label: 'Configuración',
path: '/dashboard/config',
subItems: [
{ icon: Server, label: 'APIs', path: '/dashboard/config/apis' },
{ icon: CreditCard, label: 'Pagos', path: '/dashboard/config/payments' },
{ icon: Settings, label: 'Parámetros', path: '/dashboard/config/parameters' },
{ icon: Radio, label: 'Integraciones', path: '/dashboard/config/integrations' },
{ icon: FileText, label: 'Auditoría', path: '/dashboard/config/audit' },
{ icon: Shield, label: 'Security Center', path: '/dashboard/config/security' },
{ icon: Brain, label: 'Personalización', path: '/dashboard/config/personalization' }
]
},
{ icon: BarChart3, label: 'Analytics', path: '/dashboard/analytics' },
{ icon: Search, label: 'Buscar Lugares', path: '/dashboard/search-places' },
{ icon: Store, label: 'Buscar Comercios', path: '/dashboard/search-establishments' }

View File

@@ -0,0 +1,109 @@
import React, { useState } from 'react';
import { Server, TestTube, Edit, X, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useSystemConfig } from '@/hooks/useSystemConfig';
const APIs = () => {
const [editingApi, setEditingApi] = useState<string | null>(null);
const {
apiConfigs,
loading,
updateApiConfig,
testApiConnection,
} = useSystemConfig();
const handleApiEdit = (id: string, field: string, value: string) => {
const config = apiConfigs.find(c => c.id === id);
if (config) {
updateApiConfig({ ...config, [field]: value });
setEditingApi(null);
}
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Configuración de APIs</h2>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Server className="w-5 h-5" />
APIs Externas
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex items-center justify-center p-8">
<RefreshCw className="w-6 h-6 animate-spin" />
</div>
) : (
<div className="space-y-4">
{apiConfigs.map((config) => (
<div key={config.id} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="font-medium">{config.name}</h4>
<div className="flex items-center gap-2">
<Badge variant={config.status === 'active' ? 'default' : 'secondary'}>
{config.status}
</Badge>
<Button
size="sm"
variant="outline"
onClick={() => testApiConnection(config.id)}
>
<TestTube className="w-4 h-4 mr-1" />
Probar
</Button>
</div>
</div>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<label className="font-medium">Endpoint:</label>
{editingApi === config.id + '-endpoint' ? (
<div className="flex gap-2 mt-1">
<Input
defaultValue={config.endpoint}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleApiEdit(config.id, 'endpoint', e.currentTarget.value);
}
}}
/>
<Button size="sm" onClick={() => setEditingApi(null)}>
<X className="w-4 h-4" />
</Button>
</div>
) : (
<div className="flex items-center gap-2 mt-1">
<span className="text-gray-600">{config.endpoint}</span>
<Button
size="sm"
variant="ghost"
onClick={() => setEditingApi(config.id + '-endpoint')}
>
<Edit className="w-4 h-4" />
</Button>
</div>
)}
</div>
<div>
<label className="font-medium">Timeout:</label>
<span className="text-gray-600 ml-2">{config.timeout}ms</span>
</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default APIs;

View File

@@ -0,0 +1,15 @@
import React from 'react';
import AuditLogs from '@/components/security/AuditLogs';
const Audit = () => {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Registro de Auditoría</h2>
</div>
<AuditLogs />
</div>
);
};
export default Audit;

View File

@@ -0,0 +1,88 @@
import React from 'react';
import { Wifi, RefreshCw, CheckCircle, XCircle, Clock } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useSystemConfig } from '@/hooks/useSystemConfig';
const Integrations = () => {
const {
integrations,
loading,
syncIntegration,
} = useSystemConfig();
const getStatusIcon = (status: string) => {
switch (status) {
case 'connected':
return <CheckCircle className="w-5 h-5 text-green-500" />;
case 'error':
return <XCircle className="w-5 h-5 text-red-500" />;
default:
return <Clock className="w-5 h-5 text-yellow-500" />;
}
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Integraciones</h2>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Wifi className="w-5 h-5" />
Servicios Externos
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex items-center justify-center p-8">
<RefreshCw className="w-6 h-6 animate-spin" />
</div>
) : (
<div className="space-y-4">
{integrations.map((integration) => (
<div key={integration.id} className="border rounded-lg p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{getStatusIcon(integration.status)}
<div>
<h4 className="font-medium">{integration.name}</h4>
<p className="text-sm text-gray-600">{integration.type}</p>
</div>
</div>
<div className="flex items-center gap-2">
<Badge
variant={integration.status === 'connected' ? 'default' :
integration.status === 'error' ? 'destructive' : 'secondary'}
>
{integration.status}
</Badge>
<Button
size="sm"
variant="outline"
onClick={() => syncIntegration(integration.id)}
>
<RefreshCw className="w-4 h-4 mr-1" />
Sincronizar
</Button>
</div>
</div>
{integration.lastSync && (
<div className="mt-2 text-xs text-gray-500">
Última sincronización: {new Date(integration.lastSync).toLocaleString()}
</div>
)}
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default Integrations;

View File

@@ -0,0 +1,95 @@
import React, { useState } from 'react';
import { Settings, Edit, X, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useSystemConfig } from '@/hooks/useSystemConfig';
const Parameters = () => {
const [editingParam, setEditingParam] = useState<string | null>(null);
const {
systemParameters,
loading,
updateSystemParameter,
} = useSystemConfig();
const handleParamEdit = (id: string, value: string) => {
const param = systemParameters.find(p => p.id === id);
if (param) {
updateSystemParameter({ ...param, value });
setEditingParam(null);
}
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Parámetros del Sistema</h2>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Settings className="w-5 h-5" />
Configuración Global
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex items-center justify-center p-8">
<RefreshCw className="w-6 h-6 animate-spin" />
</div>
) : (
<div className="space-y-4">
{systemParameters.map((param) => (
<div key={param.id} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<div>
<h4 className="font-medium">{param.key}</h4>
<p className="text-sm text-gray-600">{param.description}</p>
</div>
</div>
<div className="mt-2">
{editingParam === param.id ? (
<div className="flex gap-2">
<Input
defaultValue={param.value}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleParamEdit(param.id, e.currentTarget.value);
}
}}
/>
<Button size="sm" onClick={() => setEditingParam(null)}>
<X className="w-4 h-4" />
</Button>
</div>
) : (
<div className="flex items-center gap-2">
<span className="text-gray-900 font-mono bg-gray-50 px-3 py-1 rounded">
{param.value}
</span>
<Button
size="sm"
variant="ghost"
onClick={() => setEditingParam(param.id)}
>
<Edit className="w-4 h-4" />
</Button>
</div>
)}
</div>
<div className="mt-2 text-xs text-gray-500">
Tipo: {param.type}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default Parameters;

View File

@@ -0,0 +1,133 @@
import React, { useState } from 'react';
import { CreditCard, TestTube, Edit, X, RefreshCw, Eye, EyeOff } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { useSystemConfig } from '@/hooks/useSystemConfig';
const Payments = () => {
const [editingPayment, setEditingPayment] = useState<string | null>(null);
const [showSecrets, setShowSecrets] = useState<Record<string, boolean>>({});
const {
paymentConfigs,
loading,
updatePaymentConfig,
testPaymentConnection,
} = useSystemConfig();
const handlePaymentEdit = (id: string, field: string, value: string | boolean) => {
const config = paymentConfigs.find(c => c.id === id);
if (config) {
if (field === 'enabled' || field === 'testMode') {
updatePaymentConfig({ ...config, [field]: value });
} else {
updatePaymentConfig({
...config,
credentials: { ...config.credentials, [field]: value as string }
});
}
setEditingPayment(null);
}
};
const toggleSecretVisibility = (key: string) => {
setShowSecrets(prev => ({ ...prev, [key]: !prev[key] }));
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Configuración de Medios de Pago</h2>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<CreditCard className="w-5 h-5" />
Proveedores de Pago
</CardTitle>
</CardHeader>
<CardContent>
{loading ? (
<div className="flex items-center justify-center p-8">
<RefreshCw className="w-6 h-6 animate-spin" />
</div>
) : paymentConfigs.length === 0 ? (
<div className="text-center p-8 bg-gray-50 rounded-lg">
<CreditCard className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-semibold text-gray-900 mb-2">
No hay configuraciones de pago
</h3>
<p className="text-gray-600">
Configure los proveedores de pago para comenzar a procesar transacciones
</p>
</div>
) : (
<div className="space-y-6">
{paymentConfigs.map((config) => (
<div key={config.id} className="border rounded-lg p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h4 className="text-lg font-semibold">{config.name}</h4>
<p className="text-sm text-gray-600">{config.provider.toUpperCase()}</p>
</div>
<div className="flex items-center gap-2">
<Badge variant={config.status === 'active' ? 'default' : 'secondary'}>
{config.status}
</Badge>
<Badge variant={config.testMode ? 'outline' : 'default'}>
{config.testMode ? 'Test' : 'Producción'}
</Badge>
<Switch
checked={config.enabled}
onCheckedChange={(checked) =>
handlePaymentEdit(config.id, 'enabled', checked)
}
/>
<Button
size="sm"
variant="outline"
onClick={() => testPaymentConnection(config.id)}
>
<TestTube className="w-4 h-4 mr-1" />
Probar
</Button>
</div>
</div>
<div className="space-y-4">
{config.provider === 'stripe' && (
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium">Publishable Key:</label>
<div className="flex items-center gap-2 mt-1">
<span className="text-sm text-gray-600 truncate">
{config.credentials.publishableKey || 'No configurado'}
</span>
</div>
</div>
<div>
<label className="text-sm font-medium">Secret Key:</label>
<div className="flex items-center gap-2 mt-1">
<span className="text-sm text-gray-600">
{config.credentials.secretKey ? '••••••••••••' : 'No configurado'}
</span>
</div>
</div>
</div>
)}
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
);
};
export default Payments;

View File

@@ -0,0 +1,24 @@
import React from 'react';
import AIRecommendations from '@/components/personalization/AIRecommendations';
import UserPreferences from '@/components/personalization/UserPreferences';
import BehaviorAnalytics from '@/components/personalization/BehaviorAnalytics';
import SegmentManagement from '@/components/personalization/SegmentManagement';
const PersonalizationPage = () => {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Personalización</h2>
</div>
<div className="grid grid-cols-1 gap-6">
<AIRecommendations />
<UserPreferences />
<BehaviorAnalytics />
<SegmentManagement />
</div>
</div>
);
};
export default PersonalizationPage;

View File

@@ -0,0 +1,22 @@
import React from 'react';
import ThreatDetection from '@/components/security/ThreatDetection';
import AccessControl from '@/components/security/AccessControl';
import ComplianceMonitor from '@/components/security/ComplianceMonitor';
const SecurityCenter = () => {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-bold text-gray-900">Centro de Seguridad</h2>
</div>
<div className="grid grid-cols-1 gap-6">
<ThreatDetection />
<AccessControl />
<ComplianceMonitor />
</div>
</div>
);
};
export default SecurityCenter;