feat: add frag&drop

This commit is contained in:
ivannoskov
2025-12-01 07:48:35 +03:00
parent e06b53d133
commit 2ea90d1b55
2 changed files with 201 additions and 46 deletions

View File

@@ -28,7 +28,16 @@ import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { ru } from "date-fns/locale";
import { CalendarIcon, X, TrendingUp, BarChart3, LineChart } from "lucide-react";
import {
CalendarIcon,
X,
TrendingUp,
BarChart3,
LineChart,
GripVertical,
Maximize2,
Columns2,
} from "lucide-react";
import type {
TargetChannel,
AnalyticsMetric,
@@ -41,6 +50,7 @@ interface ChartConfigPanelProps {
channels: TargetChannel[];
data: SpendingAnalytics | null;
loading: boolean;
width: "full" | "half";
onBuild: (config: {
targetChannelIds: string[];
metrics: AnalyticsMetric[];
@@ -49,8 +59,10 @@ interface ChartConfigPanelProps {
grouping: DateGrouping;
}) => void;
onRemove?: () => void;
onWidthChange?: (width: "full" | "half") => void;
showRemoveButton?: boolean;
chartNumber?: number;
dragHandleProps?: any;
}
const METRICS: { value: AnalyticsMetric; label: string }[] = [
@@ -73,10 +85,13 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
channels,
data,
loading,
width,
onBuild,
onRemove,
onWidthChange,
showRemoveButton = false,
chartNumber = 1,
dragHandleProps,
}) => {
const [selectedChannelIds, setSelectedChannelIds] = useState<string[]>([]);
const [selectedMetrics, setSelectedMetrics] = useState<AnalyticsMetric[]>([]);
@@ -85,11 +100,11 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
const [grouping, setGrouping] = useState<DateGrouping>("day");
const [chartType, setChartType] = useState<"line" | "bar">("line");
const [showDataLabels, setShowDataLabels] = useState(false);
// Отслеживание изменений настроек
const [lastBuiltConfig, setLastBuiltConfig] = useState<string | null>(null);
const [isBuilt, setIsBuilt] = useState(false);
// Текущая конфигурация как строка для сравнения
const currentConfigString = JSON.stringify({
selectedChannelIds,
@@ -98,7 +113,7 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
dateTo: dateTo?.toISOString(),
grouping,
});
// Проверяем, изменились ли настройки
const hasChanges = isBuilt && lastBuiltConfig !== currentConfigString;
@@ -131,35 +146,72 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
dateTo: dateTo || null,
grouping,
});
setLastBuiltConfig(currentConfigString);
setIsBuilt(true);
};
const getButtonText = () => {
if (loading) return "Загрузка...";
if (!isBuilt) return "Построить график";
if (hasChanges) return "Обновить график";
return "График построен";
};
const isButtonDisabled = loading || (isBuilt && !hasChanges);
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle>График #{chartNumber}</CardTitle>
<CardDescription className="mt-1">
Настройте параметры и постройте график
</CardDescription>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
{/* Drag handle */}
<div
{...dragHandleProps}
className="cursor-grab active:cursor-grabbing touch-none"
>
<GripVertical className="h-5 w-5 text-muted-foreground hover:text-foreground" />
</div>
<div>
<CardTitle>График #{chartNumber}</CardTitle>
<CardDescription className="mt-1">
Настройте параметры и постройте график
</CardDescription>
</div>
</div>
<div className="flex items-center gap-1">
{/* Width toggle */}
{onWidthChange && (
<div className="flex gap-1 border rounded-md mr-2">
<Button
variant={width === "full" ? "default" : "ghost"}
size="sm"
onClick={() => onWidthChange("full")}
className="rounded-r-none"
title="Полная ширина"
>
<Maximize2 className="h-4 w-4" />
</Button>
<Button
variant={width === "half" ? "default" : "ghost"}
size="sm"
onClick={() => onWidthChange("half")}
className="rounded-l-none"
title="Половина ширины"
>
<Columns2 className="h-4 w-4" />
</Button>
</div>
)}
{/* Remove button */}
{showRemoveButton && onRemove && (
<Button variant="ghost" size="sm" onClick={onRemove}>
<X className="h-4 w-4" />
</Button>
)}
</div>
{showRemoveButton && onRemove && (
<Button variant="ghost" size="sm" onClick={onRemove}>
<X className="h-4 w-4" />
</Button>
)}
</div>
</CardHeader>
<CardContent className="space-y-4">
@@ -170,10 +222,7 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
<Label>Целевые каналы</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start"
>
<Button variant="outline" className="w-full justify-start">
{selectedChannelIds.length === 0
? "Выберите каналы"
: `Выбрано: ${selectedChannelIds.length}`}
@@ -209,10 +258,7 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
<Label>Метрики</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-start"
>
<Button variant="outline" className="w-full justify-start">
{selectedMetrics.length === 0
? "Выберите метрики"
: `Выбрано: ${selectedMetrics.length}`}
@@ -369,7 +415,9 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
<div className="space-y-2">
<Label className="invisible">Построить</Label>
<Button onClick={handleBuild} disabled={isButtonDisabled}>
<TrendingUp className={cn("mr-2 h-4 w-4", loading && "animate-pulse")} />
<TrendingUp
className={cn("mr-2 h-4 w-4", loading && "animate-pulse")}
/>
{getButtonText()}
</Button>
</div>
@@ -388,4 +436,3 @@ export const ChartConfigPanel: React.FC<ChartConfigPanelProps> = ({
</Card>
);
};