style: improve group functionality

This commit is contained in:
zhom
2025-11-25 23:33:28 +04:00
parent d90a333eb0
commit 3690ceb734
3 changed files with 98 additions and 52 deletions
+60 -17
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import type { GroupWithCount } from "@/types"; import type { GroupWithCount } from "@/types";
@@ -17,9 +18,37 @@ export function GroupBadges({
groups, groups,
isLoading, isLoading,
}: GroupBadgesProps) { }: GroupBadgesProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const [showLeftFade, setShowLeftFade] = useState(false);
const [showRightFade, setShowRightFade] = useState(false);
const checkScrollPosition = useCallback(() => {
const container = scrollContainerRef.current;
if (!container) return;
const { scrollLeft, scrollWidth, clientWidth } = container;
setShowLeftFade(scrollLeft > 0);
setShowRightFade(scrollLeft < scrollWidth - clientWidth - 1);
}, []);
useEffect(() => {
const container = scrollContainerRef.current;
if (!container) return;
checkScrollPosition();
container.addEventListener("scroll", checkScrollPosition);
const resizeObserver = new ResizeObserver(checkScrollPosition);
resizeObserver.observe(container);
return () => {
container.removeEventListener("scroll", checkScrollPosition);
resizeObserver.disconnect();
};
}, [checkScrollPosition]);
if (isLoading && !groups.length) { if (isLoading && !groups.length) {
return ( return (
<div className="flex flex-wrap gap-2 mb-4"> <div className="flex gap-2 mb-4">
<div className="flex items-center gap-2 px-4.5 py-1.5 text-xs"> <div className="flex items-center gap-2 px-4.5 py-1.5 text-xs">
Loading groups... Loading groups...
</div> </div>
@@ -28,22 +57,36 @@ export function GroupBadges({
} }
return ( return (
<div className="flex flex-wrap gap-2 mb-4"> <div className="relative mb-4">
{groups.map((group) => ( {showLeftFade && (
<Badge <div className="absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-background to-transparent pointer-events-none z-10" />
key={group.id} )}
variant={selectedGroupId === group.id ? "default" : "secondary"} {showRightFade && (
className="flex gap-2 items-center px-3 py-1 transition-colors cursor-pointer dark:hover:bg-primary/60 hover:bg-primary/80" <div className="absolute right-0 top-0 bottom-0 w-8 bg-gradient-to-l from-background to-transparent pointer-events-none z-10" />
onClick={() => { )}
onGroupSelect(selectedGroupId === group.id ? "default" : group.id); <div
}} ref={scrollContainerRef}
> className="flex gap-2 overflow-x-auto pb-2 -mb-2 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden"
<span>{group.name}</span> onScroll={checkScrollPosition}
<span className="bg-background/20 text-xs px-1.5 py-0.5 rounded-sm"> >
{group.count} {groups.map((group) => (
</span> <Badge
</Badge> key={group.id}
))} variant={selectedGroupId === group.id ? "default" : "secondary"}
className="flex gap-2 items-center px-3 py-1 transition-colors cursor-pointer dark:hover:bg-primary/60 hover:bg-primary/80 flex-shrink-0"
onClick={() => {
onGroupSelect(
selectedGroupId === group.id ? "default" : group.id,
);
}}
>
<span>{group.name}</span>
<span className="bg-background/20 text-xs px-1.5 py-0.5 rounded-sm">
{group.count}
</span>
</Badge>
))}
</div>
</div> </div>
); );
} }
+37 -34
View File
@@ -17,6 +17,7 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { import {
Table, Table,
TableBody, TableBody,
@@ -148,41 +149,43 @@ export function GroupManagementDialog({
</div> </div>
) : ( ) : (
<div className="border rounded-md"> <div className="border rounded-md">
<Table> <ScrollArea className="h-[240px]">
<TableHeader> <Table>
<TableRow> <TableHeader>
<TableHead>Name</TableHead> <TableRow>
<TableHead className="w-24">Actions</TableHead> <TableHead>Name</TableHead>
</TableRow> <TableHead className="w-24">Actions</TableHead>
</TableHeader>
<TableBody>
{groups.map((group) => (
<TableRow key={group.id}>
<TableCell className="font-medium">
{group.name}
</TableCell>
<TableCell>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleEditGroup(group)}
>
<LuPencil className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteGroup(group)}
>
<LuTrash2 className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow> </TableRow>
))} </TableHeader>
</TableBody> <TableBody>
</Table> {groups.map((group) => (
<TableRow key={group.id}>
<TableCell className="font-medium">
{group.name}
</TableCell>
<TableCell>
<div className="flex gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => handleEditGroup(group)}
>
<LuPencil className="w-4 h-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteGroup(group)}
>
<LuTrash2 className="w-4 h-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</ScrollArea>
</div> </div>
)} )}
</div> </div>
+1 -1
View File
@@ -48,7 +48,7 @@ const HomeHeader = ({
window.dispatchEvent(event); window.dispatchEvent(event);
}; };
return ( return (
<div className="flex justify-between items-center mt-4"> <div className="flex justify-between items-center mt-3">
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<button <button
type="button" type="button"