Some bug and visuals improvements.

This commit is contained in:
charlesgauthereau
2025-07-26 22:23:00 +02:00
parent 847a567c52
commit 7be542f676
21 changed files with 410 additions and 322 deletions
@@ -1,21 +1,15 @@
import { PageParams } from "@/types/next";
import * as drizzleDb from "@/db";
import { Page, PageContent, PageHeader, PageTitle } from "@/features/layout/page";
import { AdminTabs } from "@/components/wrappers/dashboard/admin/admin-tabs";
import { currentUser } from "@/lib/auth/current-user";
import { db } from "@/db";
import {eq, isNotNull, isNull} from "drizzle-orm";
import {isNull} from "drizzle-orm";
export default async function RoutePage(props: PageParams<{}>) {
const user = await currentUser();
const users = await db.query.user.findMany({
where: (fields) => isNull(fields.deletedAt)
});
// const users = await db.query.user.findMany();
console.log("users",users);
const settings = await db.query.setting.findFirst({
where: (fields, { eq }) => eq(fields.name, "system"),
@@ -26,7 +20,7 @@ export default async function RoutePage(props: PageParams<{}>) {
<PageHeader>
<PageTitle>Administration Panel</PageTitle>
</PageHeader>
<PageContent className="mt-10">
<PageContent>
<AdminTabs settings={settings!} users={users} />
</PageContent>
</Page>
@@ -27,7 +27,7 @@ export default async function RoutePage(props: PageParams<{}>) {
</PageActions>
)}
</PageHeader>
<PageContent className="mt-10">
<PageContent>
{agents.length > 0 ? (
<CardsWithPagination data={agents} cardItem={AgentCard} cardsPerPage={4} numberOfColumns={1} />
) : (
@@ -1,28 +1,20 @@
"use client";
import { authClient } from "@/lib/auth/auth-client";
import { PageParams } from "@/types/next";
import {Page, PageHeader, PageTitle} from "@/features/layout/page";
export default function RoutePage(props: PageParams<{}>) {
const { data: activeOrganization } = authClient.useActiveOrganization();
export default async function RoutePage(props: PageParams<{}>) {
return (
<Page>
<PageHeader>
<PageTitle>Dashboard</PageTitle>
</PageHeader>
<div className="flex flex-1 flex-col gap-4">
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
<div className="aspect-video rounded-xl bg-muted/50" />
</div>
{/*<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min" />*/}
{/*{JSON.stringify(activeOrganization)}*/}
{/*{activeOrganization?.name}*/}
</div>
</Page>
);
@@ -69,7 +69,12 @@ export default async function RoutePage(props: PageParams<{
numberOfColumns={1}
extendedProps={proj}
/>
) : null}
) : (
<div className="flex flex-col items-center justify-center h-full text-muted-foreground py-20">
<p className="text-lg font-medium">No databases found</p>
<p className="text-sm mt-2">You havent added any databases to this project yet.</p>
</div>
)}
</PageContent>
</Page>
);
@@ -41,7 +41,7 @@ export default async function RoutePage(props: PageParams<{ }>) {
)}
</PageHeader>
<PageContent className="mt-10">
<PageContent>
{projects.length > 0 ? (
<CardsWithPagination organizationSlug={organization.slug} data={projects} cardItem={ProjectCard}
cardsPerPage={4} numberOfColumns={1}/>
@@ -7,6 +7,9 @@ import {
DeleteOrganizationButton
} from "@/components/wrappers/dashboard/organization/DeleteOrganization/DeleteOrganizationButton";
import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/EditButtonSettings/EditButtonSettings";
import {
SettingsOrganizationMembersTable
} from "@/components/wrappers/dashboard/settings/settings-organization-members-table";
export default async function RoutePage(props: PageParams<{ slug: string }>) {
@@ -17,11 +20,14 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
notFound();
}
console.log(organization);
return (
<Page>
<PageHeader>
<PageTitle className="flex items-center">
Settings
Organization settings
{organization.slug != "default" ?
<EditButtonSettings/>
: null}
@@ -32,11 +38,11 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
: null}
</PageActions>
</PageHeader>
<PageDescription>
Manage your organization settings.
</PageDescription>
{/*<PageDescription>*/}
{/* Manage your organization settings.*/}
{/*</PageDescription>*/}
<PageContent>
{/* TODO add the list of organisation members (add, remove, edit) */}
<SettingsOrganizationMembersTable organization={organization} />
</PageContent>
</Page>
)

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@@ -1,5 +1,158 @@
"use client";
// "use client";
//
// import {
// ColumnDef,
// flexRender,
// getCoreRowModel,
// useReactTable,
// getPaginationRowModel,
// getSortedRowModel,
// SortingState,
// ColumnFiltersState,
// getFilteredRowModel,
// } from "@tanstack/react-table";
//
// import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
// import { Input } from "@/components/ui/input";
//
// import { useState } from "react";
// import { TablePagination } from "./table-pagination";
// import { Checkbox } from "@/components/ui/checkbox";
//
// interface DataTableProps<TData, TValue> {
// columns: ColumnDef<TData, TValue>[];
// data: TData[];
// enableFilter?: boolean;
// enableSelect?: boolean;
// enablePagination?: boolean;
// paginationOptions?: {
// pageSize: number[];
// pageVisible: number;
// className?: string;
// };
// filterOptions?: {
// title?: string;
// key: string;
// };
// emptyText?: string;
// }
//
// export function DataTable<TData, TValue>({
// columns,
// data,
// enableFilter = false,
// enablePagination = true,
// enableSelect = true,
// paginationOptions = { pageSize: [10, 20, 30, 40, 50, 100], pageVisible: 3 },
// filterOptions = { key: "id", title: "Filter by ID" },
// emptyText = "No data.",
// }: DataTableProps<TData, TValue>) {
// const [sorting, setSorting] = useState<SortingState>([]);
// const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
// const [rowSelection, setRowSelection] = useState({});
//
// if (enableSelect && data.length > 0) {
// const selectColumnExists = columns.some((column) => column.id === "select");
//
// if (!selectColumnExists) {
// columns.unshift({
// id: "select",
// header: ({ table }) => (
// <Checkbox
// checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
// onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
// aria-label="Select all"
// />
// ),
// cell: ({ row }) => <Checkbox checked={row.getIsSelected()} onCheckedChange={(value) => row.toggleSelected(!!value)} aria-label="Select row" />,
// enableSorting: false,
// enableHiding: false,
// });
// }
// }
//
// const table = useReactTable({
// data,
// columns,
// getCoreRowModel: getCoreRowModel(),
// getPaginationRowModel: getPaginationRowModel(),
// onSortingChange: setSorting,
// getSortedRowModel: getSortedRowModel(),
// onColumnFiltersChange: setColumnFilters,
// getFilteredRowModel: getFilteredRowModel(),
// onRowSelectionChange: setRowSelection,
// state: {
// sorting,
// columnFilters,
// rowSelection,
// },
// });
//
// return (
// <div className="h-full">
// {enableFilter && (
// <div className="flex items-center py-4">
// <Input
// placeholder={`${filterOptions.title ?? `Filter by ${filterOptions.key}`}`}
// value={(table.getColumn(filterOptions.key)?.getFilterValue() as string) ?? ""}
// onChange={(event) => table.getColumn(filterOptions.key)?.setFilterValue(event.target.value)}
// className="max-w-sm"
// />
// </div>
// )}
// <div className="rounded-md border w-full">
// <Table className="w-full">
// <TableHeader>
// {table.getHeaderGroups().map((headerGroup) => (
// <TableRow key={headerGroup.id}>
// {headerGroup.headers.map((header) => {
// return (
// <TableHead key={header.id}>
// {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
// </TableHead>
// );
// })}
// </TableRow>
// ))}
// </TableHeader>
// <TableBody>
// {table.getRowModel().rows?.length ? (
// table.getRowModel().rows.map((row) => (
// <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
// {row.getVisibleCells().map((cell) => (
// <TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
// ))}
// </TableRow>
// ))
// ) : (
// <TableRow>
// <TableCell colSpan={columns.length} className="h-24 text-center">
// {emptyText}
// </TableCell>
// </TableRow>
// )}
// </TableBody>
// </Table>
// </div>
// <div className="flex items-center justify-end space-x-2 py-4 mt-6">
// {enableSelect && (
// <div className="flex-1 text-sm text-muted-foreground">
// {table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected.
// </div>
// )}
// {enablePagination && (
// <TablePagination
// table={table}
// maxVisiblePages={paginationOptions?.pageVisible}
// pageSizeOptions={paginationOptions.pageSize}
// className={paginationOptions.className}
// />
// )}
// </div>
// </div>
// );
// }
"use client"
import {
ColumnDef,
flexRender,
@@ -12,12 +165,14 @@ import {
getFilteredRowModel,
} from "@tanstack/react-table";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
import {Input} from "@/components/ui/input";
import { useState } from "react";
import { TablePagination } from "./table-pagination";
import { Checkbox } from "@/components/ui/checkbox";
import {useState} from "react";
import {TablePagination} from "./table-pagination";
import {Checkbox} from "@/components/ui/checkbox";
import {Button} from "@/components/ui/button";
import {useRouter} from "next/navigation";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
@@ -34,37 +189,47 @@ interface DataTableProps<TData, TValue> {
title?: string;
key: string;
};
emptyText?: string;
emptyButton?: {
text: string;
variant: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
path: string;
};
highlightRow?: (row: TData) => boolean;
}
export function DataTable<TData, TValue>({
columns,
data,
enableFilter = false,
enablePagination = true,
enableSelect = true,
paginationOptions = { pageSize: [10, 20, 30, 40, 50, 100], pageVisible: 3 },
filterOptions = { key: "id", title: "Filter by ID" },
emptyText = "No data.",
}: DataTableProps<TData, TValue>) {
columns,
data,
enableFilter = false,
enablePagination = true,
enableSelect = true,
paginationOptions = {pageSize: [10, 20, 30, 40, 50, 100], pageVisible: 3},
filterOptions = {key: "id", title: "Filter by ID"},
emptyButton,
highlightRow
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [rowSelection, setRowSelection] = useState({});
if (enableSelect && data.length > 0) {
const selectColumnExists = columns.some((column) => column.id === "select");
if (!selectColumnExists) {
columns.unshift({
id: "select",
header: ({ table }) => (
header: ({table}) => (
<Checkbox
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => <Checkbox checked={row.getIsSelected()} onCheckedChange={(value) => row.toggleSelected(!!value)} aria-label="Select row" />,
cell: ({row}) => <Checkbox checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"/>,
enableSorting: false,
enableHiding: false,
});
@@ -87,9 +252,11 @@ export function DataTable<TData, TValue>({
rowSelection,
},
});
const router = useRouter();
return (
<div>
<div
className="flex flex-col h-full"
>
{enableFilter && (
<div className="flex items-center py-4">
<Input
@@ -100,55 +267,86 @@ export function DataTable<TData, TValue>({
/>
</div>
)}
<div className="rounded-md border w-full">
<Table className="w-full">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
<div className="flex flex-col justify-between h-full">
<div className="rounded-md border w-full">
<Table className="w-full">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
{emptyText}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 mt-6">
{enableSelect && (
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected.
</div>
)}
{enablePagination && (
<TablePagination
table={table}
maxVisiblePages={paginationOptions?.pageVisible}
pageSizeOptions={paginationOptions.pageSize}
className={paginationOptions.className}
/>
)}
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id}
className={highlightRow && highlightRow(row.original) ? "bg-gray-100 pointer-events-none" : ""}
data-state={row.getIsSelected() && "selected"}>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="p-0">
{emptyButton ? (
<Button
variant={emptyButton.variant}
className="btn btn-primary cursor-pointer w-full rounded-tr-none rounded-tl-none py-12 text-lg font-bold"
onClick={() => {
router.push(emptyButton.path)
}}
>
<span>{emptyButton.text}</span>
</Button>
) : (
<div className="flex items-center justify-center py-12 text-lg font-bold">
<span className="text-lg font-bold">No data available</span>
</div>
)}
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4 mt-6">
{enableSelect && table.getFilteredRowModel().rows.length >= 1 && (
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s)
selected.
</div>
)}
{enablePagination && table.getFilteredRowModel().rows.length >= 1 && (
<TablePagination
table={table}
maxVisiblePages={paginationOptions?.pageVisible}
pageSizeOptions={(() => {
const rowCount = table.getFilteredRowModel().rows.length;
const allSizes = paginationOptions.pageSize.sort((a, b) => a - b);
const validSizes = allSizes.filter((size) => size <= rowCount);
const nextSize = allSizes.find((size) => size > rowCount);
if (nextSize) validSizes.push(nextSize);
return validSizes;
})()}
className={paginationOptions.className}
/>
)}
</div>
</div>
</div>
);
}
@@ -5,26 +5,40 @@ import { SettingsEmailTab } from "@/components/wrappers/dashboard/admin/AdminEma
import { SettingsStorageTab } from "@/components/wrappers/dashboard/admin/AdminStorageTab/SettingsStorageTab";
import { AdminUsersTable } from "@/components/wrappers/dashboard/admin/admin-user-table";
import { User } from "@/db/schema/01_user";
import {Setting} from "@/db/schema/00_setting";
import { Setting } from "@/db/schema/00_setting";
import { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
export type AdminTabsProps = {
users: User[];
settings: Setting;
};
export const AdminTabs = (props: AdminTabsProps) => {
const { users, settings } = props;
export const AdminTabs = ({ users, settings }: AdminTabsProps) => {
const router = useRouter();
const searchParams = useSearchParams();
const [tab, setTab] = useState<string>(() => searchParams.get("tab") ?? "users");
useEffect(() => {
const newTab = searchParams.get("tab") ?? "users";
setTab(newTab);
}, [searchParams]);
const handleChangeTab = (value: string) => {
router.push(`?tab=${value}`);
};
return (
<Tabs defaultValue="users">
<Tabs className="h-full" value={tab} onValueChange={handleChangeTab}>
<TabsList className="w-full">
<TabsTrigger className="w-full " value="users">
<TabsTrigger className="w-full" value="users">
Users
</TabsTrigger>
<TabsTrigger className="w-full " value="email">
<TabsTrigger className="w-full" value="email">
Email
</TabsTrigger>
<TabsTrigger className="w-full " value="storage">
<TabsTrigger className="w-full" value="storage">
Storage
</TabsTrigger>
</TabsList>
@@ -13,7 +13,7 @@ export const AdminUsersTable = (props: AdminUsersTableProps) => {
<div className="flex gap-4 h-fit justify-between">
<h1>List of Portabase's users</h1>
</div>
<div className="mt-5">
<div className="mt-5 h-full">
<DataTable columns={usersColumnsAdmin} data={users} />
</div>
</div>
@@ -19,6 +19,8 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
header: "Role",
cell: ({row}) => {
const [role, setRole] = useState<string>(row.getValue("role"));
const { data: session } = useSession();
const isCurrentUser = session?.user.email === row.original.email;
const updateMutation = useMutation({
mutationFn: () => updateUserAction({id: row.original.id, data: {role: role}}),
@@ -37,7 +39,11 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
};
return (
<Badge className="cursor-pointer" onClick={() => handleUpdateRole()} variant="outline">
<Badge
className={isCurrentUser ? "cursor-not-allowed opacity-50" : "cursor-pointer"}
onClick={isCurrentUser ? undefined : () => handleUpdateRole()}
variant="outline"
>
{role}
</Badge>
);
@@ -73,23 +79,18 @@ export const usersColumnsAdmin: ColumnDef<User>[] = [
},
});
return (
<div className="flex items-center gap-2">
{!session || session?.user.email === row.original.email ? null :
<ButtonWithLoading
disabled={!session || session?.user.email === row.original.email}
variant="outline"
text=""
icon={<Trash2 color="red" size={15}/>}
onClick={async () => {
await mutation.mutateAsync();
}}
size="icon"
size="sm"
/>
}
</div>
);
},
@@ -30,7 +30,7 @@ export function AppSidebar() {
<SidebarMenuCustomMain/>
</SidebarContent>
<SidebarMenu>
<SidebarMenu className="mb-2">
<SidebarMenuItem>
<LoggedInButton />
</SidebarMenuItem>
@@ -10,7 +10,7 @@ export const SideBarFooterCredit = (props: SideBarFooterCreditProps) => {
return (
<>
{state === "expanded" && (
<div className="text-center">
<div className="text-center mb-2">
<h1 className="text-[10px]">Portabase Community Edition v{env.NEXT_PUBLIC_PROJECT_VERSION}</h1>
</div>
)}
@@ -13,10 +13,15 @@ export const ProjectCard = (props: projectCardProps) => {
const { data: project, organizationSlug } = props;
return (
<Link href={`/dashboard/projects/${project.id}`} className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md">
<Link
href={`/dashboard/projects/${project.id}`}
className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md"
>
<Card className="flex flex-row justify-between">
<div className="">
<CardHeader className="text-2xl font-bold">{project.name}</CardHeader>
<div className="flex-1 text-left">
<CardHeader className="text-2xl font-bold">
{project.name}
</CardHeader>
<CardContent>{project.databases.length} databases</CardContent>
</div>
</Card>
@@ -35,7 +35,7 @@ export const DatabaseCard = (props: databaseCardProps) => {
<Card className="flex flex-row justify-between">
<div className="flex items-center space-x-4 px-4">
<Image src="/PostgreSQL.png" alt="Database type Icon" width={60} height={60} className="object-cover ml-4" />
<Image src="/images/postgresql.png" alt="Database type Icon" width={60} height={60} className="object-cover ml-4" />
<div className="justify-between">
<div className="font-medium">Name: {database.name}</div>
@@ -1,20 +0,0 @@
"use client"
import {User, Settings, UserOrganization} from "@prisma/client";
import {SettingsUsersTab} from "@/components/wrappers/dashboard/settings/SettingsUsersTab/SettingsUsersTab";
export type SettingsTabsProps = {
currentUser: User;
users: UserOrganization[];
settings: Settings;
}
export const SettingsTabs = (props: SettingsTabsProps) => {
const {currentUser, users} = props;
return (
<SettingsUsersTab currentUser={currentUser} users={users}/>
)
}
@@ -1,97 +0,0 @@
import {usersColumns} from "@/components/wrappers/dashboard/settings/SettingsUsersTab/columns-users-settings";
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
import {flexRender, Row} from "@tanstack/react-table";
import {cn} from "@/lib/utils";
export type SettingsUsersTabProps = {
// currentUser: User;
// users: UserOrganization[]
}
export const SettingsUsersTab = (props: SettingsUsersTabProps) => {
// const {currentUser, users} = props;
return (
<div className="flex flex-col h-full py-4">
<div className="flex gap-4 h-fit justify-between">
<h1>List of organization's users</h1>
</div>
<div className="mt-5">
{/*<DataTableWithPagination*/}
{/* columns={usersColumns}*/}
{/* data={users}*/}
{/* DataTable={UsersDataTable}*/}
{/* dataTableProps={{currentUser}}*/}
{/*/>*/}
</div>
</div>
)
}
export type usersDataTableProps = {
// currentUser: User;
// table: any,
}
export const UsersDataTable = (
// {currentUser, table}: usersDataTableProps
) => {
return (
<div className="rounded-md border w-full ">
{/*<Table className="w-full">*/}
{/* <TableHeader>*/}
{/* {table.getHeaderGroups().map((headerGroup) => (*/}
{/* <TableRow key={headerGroup.id}>*/}
{/* {headerGroup.headers.map((header) => {*/}
{/* return (*/}
{/* <TableHead key={header.id}>*/}
{/* {header.isPlaceholder*/}
{/* ? null*/}
{/* : flexRender(*/}
{/* header.column.columnDef.header,*/}
{/* header.getContext()*/}
{/* )}*/}
{/* </TableHead>*/}
{/* )*/}
{/* })}*/}
{/* </TableRow>*/}
{/* ))}*/}
{/* </TableHeader>*/}
{/* <TableBody>*/}
{/* {table.getRowModel().rows?.length ? (*/}
{/* table.getRowModel().rows.map((row: Row<UserOrganization>) => {*/}
{/* return(*/}
{/* <TableRow*/}
{/* className={cn((row.original.userId) === currentUser.id ? "opacity-40 pointer-events-none" : "")}*/}
{/* key={row.id}*/}
{/* data-state={row.getIsSelected() && "selected"}*/}
{/* >*/}
{/* {row.getVisibleCells().map((cell) => (*/}
{/* <TableCell key={cell.id}>*/}
{/* {flexRender(*/}
{/* cell.column.columnDef.cell,*/}
{/* cell.getContext(),*/}
{/* )}*/}
{/* </TableCell>*/}
{/* ))}*/}
{/* </TableRow>*/}
{/* )*/}
{/* }*/}
{/* )) : (*/}
{/* <TableRow>*/}
{/* <TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">*/}
{/* No results.*/}
{/* </TableCell>*/}
{/* </TableRow>*/}
{/* )}*/}
{/* </TableBody>*/}
{/*</Table>*/}
</div>
)
}
@@ -1,60 +0,0 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { Badge } from "@/components/ui/badge";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { updateUserOrganizationAction } from "@/components/wrappers/dashboard/settings/SettingsUsersTab/settings-user-tab.action";
import {OrganizationMember} from "@/db/schema/03_member";
export const usersColumns: ColumnDef<OrganizationMember>[] = [
{
accessorKey: "role",
header: "Role",
cell: ({ row }) => {
const router = useRouter();
const [role, setRole] = useState<string>(row.getValue("role"));
const updateMutation = useMutation({
mutationFn: () => updateUserOrganizationAction({ id: row.original.id, role: role }),
onSuccess: () => {
toast.success(`User updated successfully.`);
// router.refresh()
},
onError: () => {
toast.error(`An error occurred while updating user information.`);
},
});
const handleUpdateRole = async () => {
const nextRole = role === "admin" ? "member" : role === "member" ? "admin" : "admin";
setRole(nextRole);
await updateMutation.mutateAsync();
};
return (
<Badge className="cursor-pointer" onClick={() => handleUpdateRole()} variant="outline">
{role}
</Badge>
);
},
},
{
accessorKey: "user.name",
header: "Name",
},
{
accessorKey: "user.email",
header: "Email",
},
{
accessorKey: "updatedAt",
header: "Updated At",
cell: ({ row }) => {
return new Date(row.getValue("updatedAt")).toLocaleString("fr-FR");
},
},
];
@@ -1,30 +0,0 @@
"use server"
import {userAction} from "@/safe-actions";
import {z} from "zod";
import {prisma} from "@/prisma";
export const updateUserOrganizationAction = userAction
.schema(
z.object({
id: z.string(),
role: z.string(),
}
)
)
.action(async ({parsedInput, ctx}) => {
const updatedUserOrganization = await prisma.userOrganization.update({
where: {
id: parsedInput.id,
},
data: {
role: parsedInput.role,
},
})
return {
data: updatedUserOrganization,
}
})
@@ -0,0 +1,57 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import {MemberWithUser} from "@/db/schema/02_organization";
import {useState} from "react";
import {useSession} from "@/lib/auth/auth-client";
import {useMutation} from "@tanstack/react-query";
import {updateUserAction} from "@/components/wrappers/dashboard/profile/UserForm/user-form.action";
import {toast} from "sonner";
import {Badge} from "@/components/ui/badge";
export const organizationMemberColumns: ColumnDef<MemberWithUser>[] = [
{
accessorKey: "role",
header: "Role",
cell: ({row}) => {
const [role, setRole] = useState<string>(row.getValue("role"));
const { data: session } = useSession();
const isCurrentUser = session?.user.email === row.original.user.email;
// const updateMutation = useMutation({
// mutationFn: () => updateUserAction({id: row.original.id, data: {role: role}}),
// onSuccess: () => {
// toast.success(`User updated successfully.`);
// },
// onError: () => {
// toast.error(`An error occurred while updating user information.`);
// },
// });
const handleUpdateRole = async () => {
// const nextRole = role === "admin" ? "pending" : role === "pending" ? "user" : "admin";
// setRole(nextRole);
// await updateMutation.mutateAsync();
};
return (
<Badge
className={isCurrentUser ? "cursor-not-allowed opacity-50" : "cursor-pointer"}
onClick={isCurrentUser ? undefined : () => handleUpdateRole()}
variant="outline"
>
{role}
</Badge>
);
},
},
{
accessorKey: "user.name",
header: "Name",
},
{
accessorKey: "user.email",
header: "Email",
}
];
@@ -0,0 +1,23 @@
import {DataTable} from "@/components/wrappers/common/table/data-table";
import {usersColumnsAdmin} from "@/components/wrappers/dashboard/admin/columns-users";
import {MemberWithUser, Organization, OrganizationWithMembers} from "@/db/schema/02_organization";
import {OrganizationInvitation} from "@/db/schema/04_invitation";
import {organizationMemberColumns} from "@/components/wrappers/dashboard/settings/columns-organization-members";
interface SettingsOrganizationMembersTableProps {
organization: OrganizationWithMembers
}
export const SettingsOrganizationMembersTable = ({organization}: SettingsOrganizationMembersTableProps) => {
return (
<div className="flex flex-col h-full py-4">
<div className="flex gap-4 h-fit justify-between">
<h1>List of Organization members</h1>
</div>
<div className="mt-5 h-full">
<DataTable columns={organizationMemberColumns} data={organization.members as MemberWithUser[]}/>
</div>
</div>
)
}