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,
|
type VisibilityState,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||||
|
import type React from "react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { DataTablePagination } from "~/components/custom_ui/dataTable-pagination";
|
import { DataTablePagination } from "~/components/custom_ui/dataTable-pagination";
|
||||||
import {
|
import {
|
||||||
|
|
@ -30,8 +31,9 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "~/components/ui/table";
|
} from "~/components/ui/table";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
|
import { ContextMenu, ContextMenuTrigger } from "../ui/context-menu";
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
type DataTableProps<TData, TValue> = {
|
||||||
columns: ColumnDef<TData, TValue>[];
|
columns: ColumnDef<TData, TValue>[];
|
||||||
data: TData[];
|
data: TData[];
|
||||||
pinnedFiltri?: PinnedFiltro[];
|
pinnedFiltri?: PinnedFiltro[];
|
||||||
|
|
@ -47,7 +49,23 @@ interface DataTableProps<TData, TValue> {
|
||||||
onStateChange?: () => void;
|
onStateChange?: () => void;
|
||||||
defaultFilterState?: ColumnFiltersState;
|
defaultFilterState?: ColumnFiltersState;
|
||||||
onTableInit?: (table: TableType<TData>) => void;
|
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>({
|
export function DataTable<TData, TValue>({
|
||||||
columns,
|
columns,
|
||||||
|
|
@ -65,6 +83,8 @@ export function DataTable<TData, TValue>({
|
||||||
onStateChange,
|
onStateChange,
|
||||||
defaultFilterState,
|
defaultFilterState,
|
||||||
onTableInit,
|
onTableInit,
|
||||||
|
withContextMenu,
|
||||||
|
contextMenuContent,
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const [sorting, setSorting] = useState<SortingState>(defaultSort || []);
|
const [sorting, setSorting] = useState<SortingState>(defaultSort || []);
|
||||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
|
|
@ -155,7 +175,8 @@ export function DataTable<TData, TValue>({
|
||||||
virtualizer.getVirtualItems().map((virtualRow, index) => {
|
virtualizer.getVirtualItems().map((virtualRow, index) => {
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <always exists>
|
// biome-ignore lint/style/noNonNullAssertion: <always exists>
|
||||||
const row = rows[virtualRow.index]!;
|
const row = rows[virtualRow.index]!;
|
||||||
return (
|
|
||||||
|
const rowElem = (
|
||||||
<TableRow
|
<TableRow
|
||||||
className={cn(
|
className={cn(
|
||||||
rowOnClick && "cursor-pointer",
|
rowOnClick && "cursor-pointer",
|
||||||
|
|
@ -189,6 +210,17 @@ export function DataTable<TData, TValue>({
|
||||||
})}
|
})}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (withContextMenu && contextMenuContent) {
|
||||||
|
return (
|
||||||
|
<ContextMenu key={row.id}>
|
||||||
|
<ContextMenuTrigger asChild>{rowElem}</ContextMenuTrigger>
|
||||||
|
|
||||||
|
{contextMenuContent(row.original)}
|
||||||
|
</ContextMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return rowElem;
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
Crown,
|
Crown,
|
||||||
Mail,
|
Mail,
|
||||||
MessagesSquare,
|
MessagesSquare,
|
||||||
MoreHorizontal,
|
|
||||||
Paperclip,
|
Paperclip,
|
||||||
Search,
|
Search,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
|
|
@ -19,23 +18,25 @@ import type {
|
||||||
PinnedFiltro,
|
PinnedFiltro,
|
||||||
SearchFiltro,
|
SearchFiltro,
|
||||||
} from "~/components/custom_ui/dataTable-toolbar";
|
} 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 { UserAvatar } from "~/components/user_avatar";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import type { Users } from "~/schemas/public/Users";
|
import type { Users } from "~/schemas/public/Users";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
import {
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuSeparator,
|
||||||
|
} from "../ui/context-menu";
|
||||||
|
|
||||||
type UserWChatInfo = Pick<
|
type UserWChatInfo = Pick<
|
||||||
Users,
|
Users,
|
||||||
"id" | "username" | "email" | "isAdmin" | "isBlocked" | "created_at"
|
| "id"
|
||||||
|
| "username"
|
||||||
|
| "email"
|
||||||
|
| "telefono"
|
||||||
|
| "isAdmin"
|
||||||
|
| "isBlocked"
|
||||||
|
| "created_at"
|
||||||
> & { chatid: string | null };
|
> & { chatid: string | null };
|
||||||
|
|
||||||
export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
|
|
@ -51,10 +52,10 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
|
|
||||||
const tabledata = data.map((user) => {
|
const tabledata = data.map((user) => {
|
||||||
return {
|
return {
|
||||||
actions: "Azioni",
|
|
||||||
chatid: user.chatid,
|
chatid: user.chatid,
|
||||||
created_at: user.created_at,
|
created_at: user.created_at,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
telefono: user.telefono,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
isAdmin: user.isAdmin ? "Admin" : "Utente",
|
isAdmin: user.isAdmin ? "Admin" : "Utente",
|
||||||
isBanned: user.isBlocked ? "Bloccato" : "Attivo",
|
isBanned: user.isBlocked ? "Bloccato" : "Attivo",
|
||||||
|
|
@ -68,9 +69,9 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
];
|
];
|
||||||
|
|
||||||
const columns_titles = {
|
const columns_titles = {
|
||||||
actions: "Azioni",
|
|
||||||
created_at: "Creato il",
|
created_at: "Creato il",
|
||||||
email: "Email",
|
email: "Email",
|
||||||
|
telefono: "Telefono",
|
||||||
isAdmin: "Ruolo",
|
isAdmin: "Ruolo",
|
||||||
isBanned: "Status",
|
isBanned: "Status",
|
||||||
username: "User",
|
username: "User",
|
||||||
|
|
@ -111,6 +112,30 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.email} />
|
<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",
|
accessorKey: "isAdmin",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|
@ -141,6 +166,7 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
<DataTableColumnHeader column={column} title={columns_titles.isAdmin} />
|
<DataTableColumnHeader column={column} title={columns_titles.isAdmin} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "isBanned",
|
accessorKey: "isBanned",
|
||||||
filterFn: (row, id, value) => {
|
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[] = [
|
const pinnedFiltri: PinnedFiltro[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -298,10 +207,101 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
columns_titles={columns_titles}
|
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}
|
data={tabledata}
|
||||||
|
defaultColumnVisibility={{
|
||||||
|
actions: true,
|
||||||
|
created_at: true,
|
||||||
|
email: true,
|
||||||
|
telefono: true,
|
||||||
|
isAdmin: true,
|
||||||
|
isBanned: false,
|
||||||
|
username: true,
|
||||||
|
}}
|
||||||
defaultSort={[{ desc: true, id: "created_at" }]}
|
defaultSort={[{ desc: true, id: "created_at" }]}
|
||||||
pinnedFiltri={pinnedFiltri}
|
pinnedFiltri={pinnedFiltri}
|
||||||
searchColumn={searchFiltro}
|
searchColumn={searchFiltro}
|
||||||
|
withContextMenu={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ export const getUsersWithChatInfoHandler = async ({ db }: { db: Querier }) => {
|
||||||
.select([
|
.select([
|
||||||
"users.id",
|
"users.id",
|
||||||
"users.username",
|
"users.username",
|
||||||
|
"users.telefono",
|
||||||
"users.email",
|
"users.email",
|
||||||
"users.isAdmin",
|
"users.isAdmin",
|
||||||
"users.isBlocked",
|
"users.isBlocked",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue