refactor(accounts): decompose CloudAccountList into focused components
- Extract AccountsHeader, AccountsToolbar, AddAccountDialog, ExportImportDialogs, BatchActionBar, AccountsGrid - Slim CloudAccountList from 1159 to 164 lines (orchestrator pattern) - Improve visual hierarchy: stat cards with color-tinted borders, toolbar separators, better empty state - All existing functionality preserved
This commit is contained in:
1 parent
7d2977b665
commit
bf5a42c03e
8 files changed
+1719
-1416
No files matched your search
@@ -0,0 +1,272 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Check,
|
||||
CheckSquare,
|
||||
Columns2,
|
||||
Columns3,
|
||||
Download,
|
||||
FileDown,
|
||||
LayoutGrid,
|
||||
LayoutList,
|
||||
List,
|
||||
MoreHorizontal,
|
||||
Plus,
|
||||
RefreshCcw,
|
||||
SortAsc,
|
||||
Upload,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
|
||||
interface AccountsToolbarProps {
|
||||
autoSwitchEnabled: boolean | undefined;
|
||||
isSettingsLoading: boolean;
|
||||
onToggleAutoSwitch: (checked: boolean) => void;
|
||||
isAutoSwitchPending: boolean;
|
||||
selectedCount: number;
|
||||
totalCount: number;
|
||||
onToggleSelectAll: () => void;
|
||||
onForcePoll: () => void;
|
||||
isForcePollPending: boolean;
|
||||
onSyncLocal: () => void;
|
||||
isSyncPending: boolean;
|
||||
onOpenExportDialog: () => void;
|
||||
onOpenImportDialog: () => void;
|
||||
onOpenAddDialog: () => void;
|
||||
currentSort: string;
|
||||
sortOptions: readonly string[];
|
||||
sortI18nKeys: Record<string, string>;
|
||||
onSortChange: (sort: string) => void;
|
||||
gridLayout: string;
|
||||
onLayoutChange: (layout: string) => void;
|
||||
}
|
||||
|
||||
export function AccountsToolbar(props: AccountsToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
autoSwitchEnabled,
|
||||
isSettingsLoading,
|
||||
onToggleAutoSwitch,
|
||||
isAutoSwitchPending,
|
||||
selectedCount,
|
||||
totalCount,
|
||||
onToggleSelectAll,
|
||||
onForcePoll,
|
||||
isForcePollPending,
|
||||
onSyncLocal,
|
||||
isSyncPending,
|
||||
onOpenExportDialog,
|
||||
onOpenImportDialog,
|
||||
onOpenAddDialog,
|
||||
currentSort,
|
||||
sortOptions,
|
||||
sortI18nKeys,
|
||||
onSortChange,
|
||||
gridLayout,
|
||||
onLayoutChange,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
{/* Primary Actions Group */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Button onClick={onOpenAddDialog} className="cursor-pointer shadow-sm">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t('cloud.addAccount')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onToggleSelectAll}
|
||||
title={t('cloud.batch.selectAll')}
|
||||
className={`cursor-pointer transition-colors ${selectedCount > 0 && selectedCount === totalCount ? 'border-primary/50 bg-primary/5 text-primary' : ''}`}
|
||||
>
|
||||
<CheckSquare
|
||||
className={`mr-2 h-4 w-4 ${selectedCount > 0 && selectedCount === totalCount ? 'text-primary' : 'opacity-70'}`}
|
||||
/>
|
||||
{selectedCount > 0 ? `${selectedCount} Selected` : t('cloud.batch.selectAll')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Secondary Actions & View Options */}
|
||||
<div className="flex flex-wrap items-center gap-2 sm:gap-3">
|
||||
{/* Layout Toggles */}
|
||||
<div className="hidden items-center gap-1 rounded-lg border border-white/[0.08] bg-white/[0.02] p-1 sm:flex">
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={gridLayout === 'auto' ? 'secondary' : 'ghost'}
|
||||
size="icon"
|
||||
className="h-7 w-7 cursor-pointer rounded-md"
|
||||
onClick={() => onLayoutChange('auto')}
|
||||
>
|
||||
<LayoutGrid className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('cloud.layout.auto')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={gridLayout === '2-col' ? 'secondary' : 'ghost'}
|
||||
size="icon"
|
||||
className="h-7 w-7 cursor-pointer rounded-md"
|
||||
onClick={() => onLayoutChange('2-col')}
|
||||
>
|
||||
<Columns2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('cloud.layout.twoCol')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={gridLayout === '3-col' ? 'secondary' : 'ghost'}
|
||||
size="icon"
|
||||
className="h-7 w-7 cursor-pointer rounded-md"
|
||||
onClick={() => onLayoutChange('3-col')}
|
||||
>
|
||||
<Columns3 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('cloud.layout.threeCol')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={gridLayout === 'list' ? 'secondary' : 'ghost'}
|
||||
size="icon"
|
||||
className="h-7 w-7 cursor-pointer rounded-md"
|
||||
onClick={() => onLayoutChange('list')}
|
||||
>
|
||||
<List className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('cloud.layout.list')}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant={gridLayout === 'compact' ? 'secondary' : 'ghost'}
|
||||
size="icon"
|
||||
className="h-7 w-7 cursor-pointer rounded-md"
|
||||
onClick={() => onLayoutChange('compact')}
|
||||
>
|
||||
<LayoutList className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t('cloud.layout.compact')}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
{/* Sort Dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="cursor-pointer gap-2 bg-white/[0.02]">
|
||||
<SortAsc className="h-4 w-4 opacity-70" />
|
||||
<span className="hidden text-xs sm:inline-block">{t(sortI18nKeys[currentSort])}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs tracking-wider uppercase">
|
||||
Sort By
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{sortOptions.map((option) => (
|
||||
<DropdownMenuItem
|
||||
key={option}
|
||||
className="cursor-pointer"
|
||||
onClick={() => onSortChange(option)}
|
||||
>
|
||||
{currentSort === option ? (
|
||||
<Check className="mr-2 h-4 w-4" />
|
||||
) : (
|
||||
<div className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{t(sortI18nKeys[option])}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* More Options Dropdown */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon" className="cursor-pointer bg-white/[0.02]">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-64">
|
||||
{/* Auto-Switch Toggle embedded in menu */}
|
||||
<div className="flex items-center justify-between px-3 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap
|
||||
className={`h-4 w-4 ${autoSwitchEnabled ? 'fill-yellow-500 text-yellow-500' : 'text-muted-foreground'}`}
|
||||
/>
|
||||
<Label htmlFor="auto-switch" className="cursor-pointer text-sm font-medium">
|
||||
{t('cloud.autoSwitch')}
|
||||
</Label>
|
||||
</div>
|
||||
<Switch
|
||||
id="auto-switch"
|
||||
checked={!!autoSwitchEnabled}
|
||||
onCheckedChange={onToggleAutoSwitch}
|
||||
disabled={isSettingsLoading || isAutoSwitchPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs tracking-wider uppercase">
|
||||
Actions
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem
|
||||
onClick={onForcePoll}
|
||||
disabled={isForcePollPending}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<RefreshCcw className={`mr-2 h-4 w-4 ${isForcePollPending ? 'animate-spin' : ''}`} />
|
||||
{t('cloud.checkQuota')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={onSyncLocal}
|
||||
disabled={isSyncPending}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Download className={`mr-2 h-4 w-4 ${isSyncPending ? 'animate-bounce' : ''}`} />
|
||||
{t('cloud.syncFromIDE')}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuLabel className="text-muted-foreground text-xs tracking-wider uppercase">
|
||||
Data
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={onOpenExportDialog} className="cursor-pointer">
|
||||
<FileDown className="mr-2 h-4 w-4" />
|
||||
{t('cloud.exportImport.export')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={onOpenImportDialog} className="cursor-pointer">
|
||||
<Upload className="mr-2 h-4 w-4" />
|
||||
{t('cloud.exportImport.import')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user