diff --git a/src/components/group-action-buttons.tsx b/src/components/group-action-buttons.tsx new file mode 100644 index 0000000..39ba3f3 --- /dev/null +++ b/src/components/group-action-buttons.tsx @@ -0,0 +1,73 @@ +import { Eye, EyeOff, ExternalLink, LoaderCircle, Tag, X } from "lucide-react" + +import { Button, buttonVariants } from "@/components/ui/button" +import { cn } from "@/lib/utils" + +export function InviteLinkButton({ link }: { link: string | null }) { + if (!link) { + return ( + + + Not shared + + ) + } + + return ( + + + Open invite link + + ) +} + +export function VisibilityToggleButton({ + title, + visible, + pending, + onToggle, +}: { + title: string + visible: boolean + pending: boolean + onToggle: () => void +}) { + return ( + + ) +} + +export function EditLabelsButton({ title, onClick }: { title: string; onClick: () => void }) { + return ( + + ) +} diff --git a/src/features/groups-by-label/combined-groups-table.tsx b/src/features/groups-by-label/combined-groups-table.tsx index e0dd048..460877d 100644 --- a/src/features/groups-by-label/combined-groups-table.tsx +++ b/src/features/groups-by-label/combined-groups-table.tsx @@ -1,21 +1,12 @@ import { useRouter } from "@tanstack/react-router" import { useServerFn } from "@tanstack/react-start" import type { Column } from "@tanstack/react-table" -import { - ArrowDown, - ArrowUp, - ChevronsUpDown, - ExternalLink, - Eye, - EyeOff, - LoaderCircle, - MessageCircleMore, - X, -} from "lucide-react" -import { useMemo, useState } from "react" +import { ArrowDown, ArrowUp, ChevronsUpDown, MessageCircleMore, Pencil } from "lucide-react" +import { useEffect, useMemo, useState } from "react" import { toast } from "sonner" import { EmptyState } from "@/components/empty-state" +import { InviteLinkButton, VisibilityToggleButton, EditLabelsButton } from "@/components/group-action-buttons" import { Pagination } from "@/components/pagination" import { Alert, AlertDescription } from "@/components/ui/alert" import { Badge } from "@/components/ui/badge" @@ -31,7 +22,6 @@ import { setWhatsappGroupVisibility } from "@/features/whatsapp/groups.functions import { useGroupVisibilityToggle } from "@/hooks/use-group-visibility-toggle" import type { TgGroup, TgGroupLabel, WaGroup } from "@/lib/api/types" import { createAppColumnHelper, type dashboardFeatures, useAppTable } from "@/lib/table" -import { cn } from "@/lib/utils" type CombinedGroupRowBase = { key: string @@ -83,10 +73,14 @@ export function CombinedGroupsTable({ const refreshError = tgRefreshError || waRefreshError const [editingKey, setEditingKey] = useState(null) - const displayRows = rows.map((row) => - row.platform === "telegram" - ? { ...row, group: { ...row.group, hide: resolveTgHide(row.group.telegramId, row.group.hide) } } - : { ...row, group: { ...row.group, hide: resolveWaHide(row.group.id, row.group.hide) } } + const displayRows = useMemo( + () => + rows.map((row) => + row.platform === "telegram" + ? { ...row, group: { ...row.group, hide: resolveTgHide(row.group.telegramId, row.group.hide) } } + : { ...row, group: { ...row.group, hide: resolveWaHide(row.group.id, row.group.hide) } } + ), + [rows, resolveTgHide, resolveWaHide] ) const editingRow = editingKey ? (displayRows.find((row) => row.key === editingKey) ?? null) : null @@ -141,57 +135,36 @@ export function CombinedGroupsTable({ header: "Labels", cell: ({ getValue }) => , }), - groupColumnHelper.accessor("link", { - header: "", - cell: ({ getValue }) => { - const link = getValue() - return link ? ( - event.stopPropagation()} - > - - Open invite link - - ) : ( - - - Not shared - - ) - }, - }), groupColumnHelper.display({ id: "actions", header: "", cell: ({ row }) => { + const link = row.original.link + if (row.original.platform === "whatsapp") { const group = row.original.group const pending = waUpdatingId === group.id const visible = !group.hide return ( -
event.stopPropagation()} className="flex items-center gap-1.5"> - - - +
+
+ +
+
+ void toggleWaVisibility(group.id, group.title, group.hide)} + /> +
+
+ + setEditingKey(row.original.key)} /> +
+
+ +
) } @@ -200,20 +173,33 @@ export function CombinedGroupsTable({ const pending = updatingId === group.telegramId const visible = !group.hide return ( -
event.stopPropagation()} className="flex items-center gap-1.5"> - - +
+
+ +
+
+ void toggleVisibility(group.telegramId, group.title, group.hide)} + /> +
+
+ + setEditingKey(row.original.key)} /> +
+
+ +
) }, @@ -227,8 +213,15 @@ export function CombinedGroupsTable({ data: displayRows, getRowId: (row) => row.key, initialState: { sorting: [{ id: "title", desc: false }], pagination: { pageIndex: 0, pageSize: 20 } }, + autoResetPageIndex: false, }) + useEffect(() => { + const pageCount = table.getPageCount() + if (table.state.pagination.pageIndex >= pageCount) table.setPageIndex(Math.max(0, pageCount - 1)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [displayRows.length]) + const editingGroupRef = editingRow?.platform === "telegram" ? { id: editingRow.group.telegramId, title: editingRow.title, type: "tg" as const } @@ -275,19 +268,7 @@ export function CombinedGroupsTable({ {table.getRowModel().rows.map((row) => ( - setEditingKey(row.original.key)} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault() - setEditingKey(row.original.key) - } - }} - > + {row.getAllCells().map((cell) => ( diff --git a/src/features/telegram/groups-table.tsx b/src/features/telegram/groups-table.tsx index cd70807..441d1f4 100644 --- a/src/features/telegram/groups-table.tsx +++ b/src/features/telegram/groups-table.tsx @@ -1,22 +1,12 @@ import { useRouter } from "@tanstack/react-router" import { useServerFn } from "@tanstack/react-start" import type { Column } from "@tanstack/react-table" -import { - ArrowDown, - ArrowUp, - ChevronsUpDown, - ExternalLink, - Eye, - EyeOff, - LoaderCircle, - type LucideIcon, - MessageCircleMore, - X, -} from "lucide-react" -import { useMemo, useState } from "react" +import { ArrowDown, ArrowUp, ChevronsUpDown, type LucideIcon, MessageCircleMore } from "lucide-react" +import { useEffect, useMemo, useState } from "react" import { toast } from "sonner" import { EmptyState } from "@/components/empty-state" +import { InviteLinkButton, VisibilityToggleButton, EditLabelsButton } from "@/components/group-action-buttons" import { Pagination } from "@/components/pagination" import { Alert, AlertDescription } from "@/components/ui/alert" import { Badge } from "@/components/ui/badge" @@ -29,11 +19,10 @@ import { LeaveGroupDialog } from "@/features/telegram/leave-group-dialog" import { useGroupVisibilityToggle } from "@/hooks/use-group-visibility-toggle" import type { TgGroup, TgGroupLabel } from "@/lib/api/types" import { createAppColumnHelper, type dashboardFeatures, useAppTable } from "@/lib/table" -import { cn } from "@/lib/utils" const groupColumnHelper = createAppColumnHelper() -/** The interactive groups table (visibility toggle, invite link, leave, click-to-edit-labels), given an already-filtered group list. */ +/** The interactive groups table (visibility toggle, invite link, leave, edit labels), given an already-filtered group list. */ export function GroupsTable({ groups: loadedGroups, allLabels, @@ -56,10 +45,15 @@ export function GroupsTable({ ) const [editingGroup, setEditingGroup] = useState(null) - const groups = loadedGroups.map((group) => ({ - ...group, - hide: resolveHide(group.telegramId, group.hide), - })) + // Keep the data reference stable across table-state renders; TanStack Table resets pagination when data changes. + const groups = useMemo( + () => + loadedGroups.map((group) => ({ + ...group, + hide: resolveHide(group.telegramId, group.hide), + })), + [loadedGroups, resolveHide] + ) const columns = useMemo(() => { const sortableHeader = ( @@ -114,30 +108,6 @@ export function GroupsTable({ header: "Labels", cell: ({ row }) => , }), - groupColumnHelper.display({ - id: "invite", - header: "", - cell: ({ row }) => { - const link = row.original.link - return link ? ( - event.stopPropagation()} - > - - Open invite link - - ) : ( - - - Not shared - - ) - }, - }), groupColumnHelper.display({ id: "actions", header: "", @@ -146,20 +116,24 @@ export function GroupsTable({ const pending = updatingId === group.telegramId const visible = !group.hide return ( -
event.stopPropagation()} className="flex items-center gap-1.5"> - - +
+
+ +
+
+ void toggleVisibility(group.telegramId, group.title, group.hide)} + /> +
+
+ setEditingGroup(group)} /> +
+
+ +
) }, @@ -175,8 +149,15 @@ export function GroupsTable({ sorting: [{ id: "title", desc: false }], pagination: { pageIndex: 0, pageSize: 20 }, }, + autoResetPageIndex: false, }) + useEffect(() => { + const pageCount = table.getPageCount() + if (table.state.pagination.pageIndex >= pageCount) table.setPageIndex(Math.max(0, pageCount - 1)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [groups.length]) + return ( <> {mutationError && ( @@ -215,19 +196,7 @@ export function GroupsTable({ {table.getRowModel().rows.map((row) => ( - setEditingGroup(row.original)} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault() - setEditingGroup(row.original) - } - }} - > + {row.getAllCells().map((cell) => ( diff --git a/src/features/telegram/users-page.tsx b/src/features/telegram/users-page.tsx index e50a4fa..882ce9f 100644 --- a/src/features/telegram/users-page.tsx +++ b/src/features/telegram/users-page.tsx @@ -103,7 +103,10 @@ export function TelegramUsersPage({ users }: { users: TgUser[] }) { {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( - + {header.isPlaceholder ? null : } ))} @@ -134,7 +137,10 @@ export function TelegramUsersPage({ users }: { users: TgUser[] }) { }} > {row.getAllCells().map((cell) => ( - + ))} diff --git a/src/features/whatsapp/create-edit-group-dialog.tsx b/src/features/whatsapp/create-edit-group-dialog.tsx index 5a28b13..282a047 100644 --- a/src/features/whatsapp/create-edit-group-dialog.tsx +++ b/src/features/whatsapp/create-edit-group-dialog.tsx @@ -107,7 +107,19 @@ export function CreateEditGroupDialog({ if (!nextOpen) reset() }} > - : - - +
+
+ +
+
+ void toggleVisibility(group.id, group.title, group.hide)} + /> +
+
+ + setEditingLabelsGroup(group)} /> +
+
+ +
) }, @@ -200,8 +165,15 @@ export function WhatsappGroupsPage({ data: visibleGroups, getRowId: (group) => String(group.id), initialState: { sorting: [{ id: "title", desc: false }], pagination: { pageIndex: 0, pageSize: 20 } }, + autoResetPageIndex: false, }) + useEffect(() => { + const pageCount = table.getPageCount() + if (table.state.pagination.pageIndex >= pageCount) table.setPageIndex(Math.max(0, pageCount - 1)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visibleGroups.length]) + return (
{mutationError && ( @@ -316,19 +288,7 @@ export function WhatsappGroupsPage({ {table.getRowModel().rows.map((row) => ( - setEditingLabelsGroup(row.original)} - onKeyDown={(event) => { - if (event.key === "Enter" || event.key === " ") { - event.preventDefault() - setEditingLabelsGroup(row.original) - } - }} - > + {row.getAllCells().map((cell) => (