feat: purchase-plans global update

This commit is contained in:
ivannoskov
2026-02-02 21:01:11 +03:00
parent f9c334ba22
commit 735077e707
11 changed files with 2575 additions and 534 deletions

View File

@@ -0,0 +1,148 @@
import { X, Check } from "lucide-react";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { ALL_COLUMNS, COLUMN_GROUPS, DEFAULT_VISIBLE_COLUMNS } from "@/lib/config/placement-columns";
interface ColumnSelectorDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
visibleColumns: Set<string>;
onVisibleColumnsChange: (columns: Set<string>) => void;
}
export function ColumnSelectorDialog({
open,
onOpenChange,
visibleColumns,
onVisibleColumnsChange,
}: ColumnSelectorDialogProps) {
const handleToggleColumn = (columnId: string) => {
const next = new Set(visibleColumns);
if (next.has(columnId)) {
// Don't allow hiding required columns
const column = ALL_COLUMNS.find((c) => c.id === columnId);
if (column?.required) return;
next.delete(columnId);
} else {
next.add(columnId);
}
onVisibleColumnsChange(next);
};
const handleToggleGroup = (groupId: string, select: boolean) => {
const groupColumns = ALL_COLUMNS.filter((c) => c.group === groupId);
const next = new Set(visibleColumns);
groupColumns.forEach((column) => {
if (column.required) return;
if (select) {
next.add(column.id);
} else {
next.delete(column.id);
}
});
onVisibleColumnsChange(next);
};
const isGroupFullySelected = (groupId: string): boolean => {
const groupColumns = ALL_COLUMNS.filter((c) => c.group === groupId);
return groupColumns.every((c) => c.required || visibleColumns.has(c.id));
};
const isGroupPartiallySelected = (groupId: string): boolean => {
const groupColumns = ALL_COLUMNS.filter((c) => c.group === groupId);
return groupColumns.some((c) => visibleColumns.has(c.id)) && !isGroupFullySelected(groupId);
};
const handleReset = () => {
onVisibleColumnsChange(new Set(DEFAULT_VISIBLE_COLUMNS));
};
const handleSelectAll = () => {
onVisibleColumnsChange(new Set(ALL_COLUMNS.map((c) => c.id)));
};
const handleSelectNone = () => {
onVisibleColumnsChange(
new Set(ALL_COLUMNS.filter((c) => c.required).map((c) => c.id))
);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Выбор колонок</DialogTitle>
<DialogDescription>
Выберите колонки для отображения в таблице размещений
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
{/* Quick actions */}
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={handleReset}>
По умолчанию
</Button>
<Button variant="outline" size="sm" onClick={handleSelectAll}>
Выбрать все
</Button>
<Button variant="outline" size="sm" onClick={handleSelectNone}>
Снять выбор
</Button>
</div>
{/* Column groups */}
{COLUMN_GROUPS.map((group) => {
const groupColumns = ALL_COLUMNS.filter((c) => c.group === group.id);
return (
<div key={group.id} className="space-y-3">
<div className="flex items-center gap-3">
<Checkbox
id={`group-${group.id}`}
checked={isGroupFullySelected(group.id)}
onCheckedChange={(checked) => handleToggleGroup(group.id, !!checked)}
/>
<label
htmlFor={`group-${group.id}`}
className="text-sm font-medium cursor-pointer"
>
{group.label}
</label>
{isGroupPartiallySelected(group.id) && (
<span className="text-xs text-muted-foreground">(частично выбрано)</span>
)}
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 ml-6">
{groupColumns.map((column) => (
<div key={column.id} className="flex items-center gap-2">
<Checkbox
id={`col-${column.id}`}
checked={visibleColumns.has(column.id)}
onCheckedChange={() => handleToggleColumn(column.id)}
disabled={column.required}
/>
<label
htmlFor={`col-${column.id}`}
className={cn(
"text-xs cursor-pointer truncate",
column.required && "font-medium"
)}
>
{column.label}
</label>
</div>
))}
</div>
</div>
);
})}
</div>
</DialogContent>
</Dialog>
);
}