feat: add telefono field to UsersTable and user controller for enhanced user information
This commit is contained in:
parent
0619ca0c34
commit
0759f4216f
3 changed files with 166 additions and 133 deletions
|
|
@ -14,6 +14,7 @@ import {
|
|||
type VisibilityState,
|
||||
} from "@tanstack/react-table";
|
||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||
import type React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { DataTablePagination } from "~/components/custom_ui/dataTable-pagination";
|
||||
import {
|
||||
|
|
@ -30,8 +31,9 @@ import {
|
|||
TableRow,
|
||||
} from "~/components/ui/table";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { ContextMenu, ContextMenuTrigger } from "../ui/context-menu";
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pinnedFiltri?: PinnedFiltro[];
|
||||
|
|
@ -47,7 +49,23 @@ interface DataTableProps<TData, TValue> {
|
|||
onStateChange?: () => void;
|
||||
defaultFilterState?: ColumnFiltersState;
|
||||
onTableInit?: (table: TableType<TData>) => void;
|
||||
}
|
||||
} & (
|
||||
| {
|
||||
withContextMenu: true;
|
||||
|
||||
/**
|
||||
* @example
|
||||
* <ContextMenuContent>
|
||||
* <ContextMenuItem>{data}<ContextMenuItem>
|
||||
* <ContextMenuContent>
|
||||
*/
|
||||
contextMenuContent: (data: TData) => React.ReactNode;
|
||||
}
|
||||
| {
|
||||
withContextMenu?: false;
|
||||
contextMenuContent?: never;
|
||||
}
|
||||
);
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
|
|
@ -65,6 +83,8 @@ export function DataTable<TData, TValue>({
|
|||
onStateChange,
|
||||
defaultFilterState,
|
||||
onTableInit,
|
||||
withContextMenu,
|
||||
contextMenuContent,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = useState<SortingState>(defaultSort || []);
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||
|
|
@ -155,7 +175,8 @@ export function DataTable<TData, TValue>({
|
|||
virtualizer.getVirtualItems().map((virtualRow, index) => {
|
||||
// biome-ignore lint/style/noNonNullAssertion: <always exists>
|
||||
const row = rows[virtualRow.index]!;
|
||||
return (
|
||||
|
||||
const rowElem = (
|
||||
<TableRow
|
||||
className={cn(
|
||||
rowOnClick && "cursor-pointer",
|
||||
|
|
@ -189,6 +210,17 @@ export function DataTable<TData, TValue>({
|
|||
})}
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
if (withContextMenu && contextMenuContent) {
|
||||
return (
|
||||
<ContextMenu key={row.id}>
|
||||
<ContextMenuTrigger asChild>{rowElem}</ContextMenuTrigger>
|
||||
|
||||
{contextMenuContent(row.original)}
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
return rowElem;
|
||||
})
|
||||
) : (
|
||||
<TableRow>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
Crown,
|
||||
Mail,
|
||||
MessagesSquare,
|
||||
MoreHorizontal,
|
||||
Paperclip,
|
||||
Search,
|
||||
ShoppingBag,
|
||||
|
|
@ -19,23 +18,25 @@ import type {
|
|||
PinnedFiltro,
|
||||
SearchFiltro,
|
||||
} from "~/components/custom_ui/dataTable-toolbar";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "~/components/ui/dropdown-menu";
|
||||
import { UserAvatar } from "~/components/user_avatar";
|
||||
import { cn } from "~/lib/utils";
|
||||
import type { Users } from "~/schemas/public/Users";
|
||||
import { api } from "~/utils/api";
|
||||
import {
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
} from "../ui/context-menu";
|
||||
|
||||
type UserWChatInfo = Pick<
|
||||
Users,
|
||||
"id" | "username" | "email" | "isAdmin" | "isBlocked" | "created_at"
|
||||
| "id"
|
||||
| "username"
|
||||
| "email"
|
||||
| "telefono"
|
||||
| "isAdmin"
|
||||
| "isBlocked"
|
||||
| "created_at"
|
||||
> & { chatid: string | null };
|
||||
|
||||
export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||
|
|
@ -51,10 +52,10 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
|||
|
||||
const tabledata = data.map((user) => {
|
||||
return {
|
||||
actions: "Azioni",
|
||||
chatid: user.chatid,
|
||||
created_at: user.created_at,
|
||||
email: user.email,
|
||||
telefono: user.telefono,
|
||||
id: user.id,
|
||||
isAdmin: user.isAdmin ? "Admin" : "Utente",
|
||||
isBanned: user.isBlocked ? "Bloccato" : "Attivo",
|
||||
|
|
@ -68,9 +69,9 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
|||
];
|
||||
|
||||
const columns_titles = {
|
||||
actions: "Azioni",
|
||||
created_at: "Creato il",
|
||||
email: "Email",
|
||||
telefono: "Telefono",
|
||||
isAdmin: "Ruolo",
|
||||
isBanned: "Status",
|
||||
username: "User",
|
||||
|
|
@ -111,6 +112,30 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
|||
<DataTableColumnHeader column={column} title={columns_titles.email} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "telefono",
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={columns_titles.telefono}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
cell: ({ row }) => {
|
||||
const date = row.getValue("created_at");
|
||||
return new Date(date as Date).toLocaleDateString();
|
||||
},
|
||||
enableHiding: false,
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={columns_titles.created_at}
|
||||
/>
|
||||
),
|
||||
sortingFn: "datetime",
|
||||
},
|
||||
{
|
||||
accessorKey: "isAdmin",
|
||||
cell: ({ row }) => {
|
||||
|
|
@ -141,6 +166,7 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
|||
<DataTableColumnHeader column={column} title={columns_titles.isAdmin} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
accessorKey: "isBanned",
|
||||
filterFn: (row, id, value) => {
|
||||
|
|
@ -153,123 +179,6 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
|||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
cell: ({ row }) => {
|
||||
const date = row.getValue("created_at");
|
||||
return new Date(date as Date).toLocaleDateString();
|
||||
},
|
||||
enableHiding: false,
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader
|
||||
column={column}
|
||||
title={columns_titles.created_at}
|
||||
/>
|
||||
),
|
||||
sortingFn: "datetime",
|
||||
},
|
||||
{
|
||||
accessorKey: "actions",
|
||||
cell: ({ row }) => {
|
||||
const user = row.original;
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="size-8 p-0" variant="ghost">
|
||||
<span className="sr-only">Apri menu</span>
|
||||
<MoreHorizontal className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Azioni</DropdownMenuLabel>
|
||||
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Ricerca"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/ricerca/${user.id}`}
|
||||
>
|
||||
<Search className="text-muted-foreground" /> Ricerca
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Comunicazioni"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/comunicazioni/${user.id}`}
|
||||
>
|
||||
<Mail className="text-muted-foreground" /> Comunicazioni
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Profilo"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/edit-user/${user.id}`}
|
||||
>
|
||||
<UserCog className="text-muted-foreground" /> Profilo
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Ordini"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/ordini/${user.id}`}
|
||||
>
|
||||
<ShoppingBag className="text-muted-foreground" /> Ordini
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Allegati"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/allegati/${user.id}`}
|
||||
>
|
||||
<Paperclip className="text-muted-foreground" /> Allegati
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Chat"
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2",
|
||||
!user.chatid && "text-neutral-200",
|
||||
)}
|
||||
href={
|
||||
user.chatid
|
||||
? {
|
||||
pathname: "/area-riservata/admin/chats",
|
||||
query: { chatId: user.chatid },
|
||||
}
|
||||
: "/area-riservata/admin/chats"
|
||||
}
|
||||
>
|
||||
<MessagesSquare /> Chat
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<button
|
||||
aria-label="Login as User"
|
||||
className="flex w-full cursor-pointer items-center gap-2"
|
||||
onClick={async () => await swap({ id: user.id })}
|
||||
type="button"
|
||||
>
|
||||
<User /> Login
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
},
|
||||
enableHiding: false,
|
||||
enableSorting: false,
|
||||
header: ({ column }) => (
|
||||
<DataTableColumnHeader column={column} title={columns_titles.actions} />
|
||||
),
|
||||
},
|
||||
];
|
||||
const pinnedFiltri: PinnedFiltro[] = [
|
||||
{
|
||||
|
|
@ -298,10 +207,101 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
|||
<DataTable
|
||||
columns={columns}
|
||||
columns_titles={columns_titles}
|
||||
contextMenuContent={(data) => (
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Ricerca"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/ricerca/${data.id}`}
|
||||
>
|
||||
<Search className="text-muted-foreground" /> Ricerca
|
||||
</Link>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Comunicazioni"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/comunicazioni/${data.id}`}
|
||||
>
|
||||
<Mail className="text-muted-foreground" /> Comunicazioni
|
||||
</Link>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Profilo"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/edit-user/${data.id}`}
|
||||
>
|
||||
<UserCog className="text-muted-foreground" /> Profilo
|
||||
</Link>
|
||||
</ContextMenuItem>
|
||||
|
||||
<ContextMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Ordini"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/ordini/${data.id}`}
|
||||
>
|
||||
<ShoppingBag className="text-muted-foreground" /> Ordini
|
||||
</Link>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Allegati"
|
||||
className="flex w-full items-center gap-2"
|
||||
href={`/area-riservata/admin/user-view/allegati/${data.id}`}
|
||||
>
|
||||
<Paperclip className="text-muted-foreground" /> Allegati
|
||||
</Link>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem>
|
||||
<Link
|
||||
aria-label="Visualizza Chat"
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2",
|
||||
!data.chatid && "text-neutral-200",
|
||||
)}
|
||||
href={
|
||||
data.chatid
|
||||
? {
|
||||
pathname: "/area-riservata/admin/chats",
|
||||
query: { chatId: data.chatid },
|
||||
}
|
||||
: "/area-riservata/admin/chats"
|
||||
}
|
||||
>
|
||||
<MessagesSquare /> Chat
|
||||
</Link>
|
||||
</ContextMenuItem>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem>
|
||||
<button
|
||||
aria-label="Login as User"
|
||||
className="flex w-full cursor-pointer items-center gap-2"
|
||||
onClick={async () => await swap({ id: data.id })}
|
||||
type="button"
|
||||
>
|
||||
<User /> Login
|
||||
</button>
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
)}
|
||||
data={tabledata}
|
||||
defaultColumnVisibility={{
|
||||
actions: true,
|
||||
created_at: true,
|
||||
email: true,
|
||||
telefono: true,
|
||||
isAdmin: true,
|
||||
isBanned: false,
|
||||
username: true,
|
||||
}}
|
||||
defaultSort={[{ desc: true, id: "created_at" }]}
|
||||
pinnedFiltri={pinnedFiltri}
|
||||
searchColumn={searchFiltro}
|
||||
withContextMenu={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ export const getUsersWithChatInfoHandler = async ({ db }: { db: Querier }) => {
|
|||
.select([
|
||||
"users.id",
|
||||
"users.username",
|
||||
"users.telefono",
|
||||
"users.email",
|
||||
"users.isAdmin",
|
||||
"users.isBlocked",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue