refactor: sider bar
This commit is contained in:
@@ -178,7 +178,7 @@ function DraggableAttribute({ id, label }: { id: string; label: string }) {
|
|||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [selectedGroupId, setSelectedGroupId] = useState(ATTRIBUTE_GROUPS[0].id);
|
const [selectedGroupIds, setSelectedGroupIds] = useState<string[]>([ATTRIBUTE_GROUPS[0].id]);
|
||||||
|
|
||||||
const filteredGroups = useMemo(() => {
|
const filteredGroups = useMemo(() => {
|
||||||
if (search.trim()) {
|
if (search.trim()) {
|
||||||
@@ -191,37 +191,14 @@ export function Sidebar() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Khi không search, chỉ hiển thị nhóm được chọn
|
// Khi không search, chỉ hiển thị nhóm được chọn
|
||||||
return ATTRIBUTE_GROUPS.filter(g => g.id === selectedGroupId);
|
return ATTRIBUTE_GROUPS.filter(g => selectedGroupIds.includes(g.id));
|
||||||
}, [search, selectedGroupId]);
|
}, [search, selectedGroupIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-72 border-r bg-gray-50/50 flex flex-col h-full">
|
<div className="w-72 border-r bg-gray-50/50 flex flex-col h-full">
|
||||||
<div className="p-4 border-b bg-white flex flex-col gap-3">
|
<div className="p-4 border-b bg-white flex flex-col gap-3">
|
||||||
<h3 className="font-semibold">Trường dữ liệu</h3>
|
|
||||||
|
|
||||||
{/* Dropdown chọn nhóm */}
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
<label className="text-xs font-medium text-muted-foreground">Nhóm dữ liệu</label>
|
|
||||||
<select
|
|
||||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary"
|
|
||||||
value={selectedGroupId}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSelectedGroupId(e.target.value);
|
|
||||||
setSearch(""); // Reset search khi chuyển nhóm
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{Array.from(new Set(ATTRIBUTE_GROUPS.map(g => g.category))).map(category => (
|
|
||||||
<optgroup key={category} label={category}>
|
|
||||||
{ATTRIBUTE_GROUPS.filter(g => g.category === category).map(g => (
|
|
||||||
<option key={g.id} value={g.id}>{g.name}</option>
|
|
||||||
))}
|
|
||||||
</optgroup>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Ô tìm kiếm */}
|
{/* Ô tìm kiếm */}
|
||||||
<div className="relative mt-1">
|
<div className="relative">
|
||||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
@@ -231,6 +208,52 @@ export function Sidebar() {
|
|||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Chips chọn nhóm */}
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
{selectedGroupIds.length > 0 && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedGroupIds([])}
|
||||||
|
className="text-[10px] text-muted-foreground hover:text-primary transition-colors"
|
||||||
|
>
|
||||||
|
Bỏ chọn tất cả
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex flex-col gap-3 max-h-[35vh] overflow-y-auto pb-1 pr-1 scrollbar-thin scrollbar-thumb-gray-200">
|
||||||
|
{Array.from(new Set(ATTRIBUTE_GROUPS.map(g => g.category))).map(category => (
|
||||||
|
<div key={category} className="flex flex-col gap-1.5">
|
||||||
|
<div className="text-[10px] font-semibold text-muted-foreground uppercase tracking-wider">{category}</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{ATTRIBUTE_GROUPS.filter(g => g.category === category).map(g => {
|
||||||
|
const isSelected = selectedGroupIds.includes(g.id);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={g.id}
|
||||||
|
onClick={() => {
|
||||||
|
if (isSelected) {
|
||||||
|
setSelectedGroupIds(prev => prev.filter(id => id !== g.id));
|
||||||
|
} else {
|
||||||
|
setSelectedGroupIds(prev => [...prev, g.id]);
|
||||||
|
}
|
||||||
|
setSearch(""); // Reset search khi chuyển nhóm
|
||||||
|
}}
|
||||||
|
className={`inline-flex items-center px-2.5 py-0.5 text-[11px] font-medium rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${
|
||||||
|
isSelected
|
||||||
|
? "border-transparent bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm"
|
||||||
|
: "border-input bg-background hover:bg-accent hover:text-accent-foreground"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{g.name}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
|
<div className="flex-1 overflow-y-auto p-4 flex flex-col gap-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user