feat: add frag&drop
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user