diff --git a/frontend/src/pages/SettingsPlugins.tsx b/frontend/src/pages/SettingsPlugins.tsx index e472177..ff406a5 100644 --- a/frontend/src/pages/SettingsPlugins.tsx +++ b/frontend/src/pages/SettingsPlugins.tsx @@ -1 +1,255 @@ -§§include(/tmp/leocrm-changes/frontend/src/pages/SettingsPlugins.tsx) \ No newline at end of file +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { usePlugins, useInstallPlugin, useActivatePlugin, useDeactivatePlugin, useUninstallPlugin } from '@/api/hooks'; +import { Button } from '@/components/ui/Button'; +import { Card } from '@/components/ui/Card'; +import { Badge } from '@/components/ui/Badge'; +import { EmptyState } from '@/components/ui/EmptyState'; +import { Skeleton } from '@/components/ui/Skeleton'; +import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; +import { useToast } from '@/components/ui/Toast'; + +interface Plugin { + name: string; + display_name?: string; + description?: string; + version?: string; + status: 'discovered' | 'installed' | 'active' | 'inactive'; + installed?: boolean; + active?: boolean; +} + +function statusBadgeVariant(status: string): 'secondary' | 'primary' | 'success' | 'warning' { + switch (status) { + case 'active': + return 'success'; + case 'installed': + case 'inactive': + return 'warning'; + default: + return 'secondary'; + } +} + +function statusLabel(status: string, t: (k: string) => string): string { + switch (status) { + case 'active': + return t('settings.pluginActive'); + case 'installed': + return t('settings.pluginInstalled'); + case 'inactive': + return t('settings.pluginInactive'); + default: + return t('settings.pluginDiscovered'); + } +} + +export function SettingsPluginsPage() { + const { t } = useTranslation(); + const toast = useToast(); + const { data, isLoading, isError, error, refetch } = usePlugins(); + const installMutation = useInstallPlugin(); + const activateMutation = useActivatePlugin(); + const deactivateMutation = useDeactivatePlugin(); + const uninstallMutation = useUninstallPlugin(); + + const [confirmUninstall, setConfirmUninstall] = useState(null); + const [confirmRemoveData, setConfirmRemoveData] = useState(false); + + const plugins: Plugin[] = data?.plugins ?? data ?? []; + + const handleInstall = async (plugin: Plugin) => { + try { + await installMutation.mutateAsync(plugin.name); + toast.success(t('settings.pluginInstalledSuccess')); + } catch (err: any) { + toast.error(err.message || t('common.error')); + } + }; + + const handleActivate = async (plugin: Plugin) => { + try { + await activateMutation.mutateAsync(plugin.name); + toast.success(t('settings.pluginActivatedSuccess')); + } catch (err: any) { + toast.error(err.message || t('common.error')); + } + }; + + const handleDeactivate = async (plugin: Plugin) => { + try { + await deactivateMutation.mutateAsync(plugin.name); + toast.success(t('settings.pluginDeactivatedSuccess')); + } catch (err: any) { + toast.error(err.message || t('common.error')); + } + }; + + const handleUninstall = async () => { + if (!confirmUninstall) return; + try { + await uninstallMutation.mutateAsync({ name: confirmUninstall.name, removeData: confirmRemoveData }); + toast.success(t('settings.pluginUninstalledSuccess')); + setConfirmUninstall(null); + setConfirmRemoveData(false); + } catch (err: any) { + toast.error(err.message || t('common.error')); + } + }; + + if (isLoading) { + return ( +
+ +
+ + + +
+
+ ); + } + + if (isError) { + return ( +
+

{t('settings.plugins')}

+ refetch()}>{t('common.reset')}} + > +

{error?.message || t('common.error')}

+
+
+ ); + } + + return ( +
+
+

{t('settings.plugins')}

+ +
+ + {plugins.length === 0 ? ( + + ) : ( +
+ {plugins.map((plugin) => { + const status = plugin.active ? 'active' : plugin.installed ? 'inactive' : plugin.status || 'discovered'; + const isBusy = + installMutation.isPending || + activateMutation.isPending || + deactivateMutation.isPending || + uninstallMutation.isPending; + + return ( + +
+
+
+

+ {plugin.display_name || plugin.name} +

+ + {statusLabel(status, t)} + + {plugin.version && ( + v{plugin.version} + )} +
+ {plugin.description && ( +

{plugin.description}

+ )} +
+
+ {status === 'discovered' && ( + + )} + {status === 'inactive' && ( + <> + + + + )} + {status === 'active' && ( + <> + + + + )} +
+
+
+ ); + })} +
+ )} + + { + setConfirmUninstall(null); + setConfirmRemoveData(false); + }} + > + + +
+ ); +}