379 lines
10 KiB
TypeScript
379 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import type { ColumnDef, RowSelectionState } from "@tanstack/react-table";
|
|
import {
|
|
flexRender,
|
|
getCoreRowModel,
|
|
useReactTable,
|
|
} from "@tanstack/react-table";
|
|
import { Text } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog";
|
|
import { Label } from "@/components/ui/label";
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface User {
|
|
firstName?: string;
|
|
lastName?: string;
|
|
image?: string;
|
|
company?: string;
|
|
jobTitle?: string;
|
|
email?: string;
|
|
}
|
|
|
|
const DUMMY_USERS: User[] = [
|
|
{
|
|
firstName: "الینا",
|
|
image: "/block-media/shadcnblocks/block/avatar/avatar1.jpg",
|
|
company: "آکمه",
|
|
jobTitle: "طراح تجربه کاربری",
|
|
email: "elina.brooks@acme.com",
|
|
},
|
|
{
|
|
firstName: "ناهید",
|
|
lastName: "پارسا",
|
|
image: "/block-media/shadcnblocks/block/avatar/avatar2.jpg",
|
|
jobTitle: "مهندس بکاند",
|
|
},
|
|
{
|
|
firstName: "مایا",
|
|
lastName: "تهرانی",
|
|
jobTitle: "مدیر خلاقیت",
|
|
email: "maya@pixelworks.io",
|
|
},
|
|
{
|
|
firstName: "امیر",
|
|
lastName: "رضایی",
|
|
image: "/block-media/shadcnblocks/block/avatar/avatar4.jpg",
|
|
company: "استریملاین",
|
|
},
|
|
{
|
|
lastName: "احمدی",
|
|
image: "/block-media/shadcnblocks/block/avatar/avatar5.jpg",
|
|
company: "کلودنست",
|
|
jobTitle: "مدیر محصول",
|
|
},
|
|
{
|
|
firstName: "آوا",
|
|
company: "وندورا",
|
|
jobTitle: "توسعهدهنده فرانتاند",
|
|
email: "ava.wright@vendora.com",
|
|
},
|
|
];
|
|
|
|
interface LetterAvatarProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
const LetterAvatar = ({ children }: LetterAvatarProps) => {
|
|
return (
|
|
<span className="flex size-6 items-center justify-center rounded-md border bg-muted text-xs">
|
|
{children}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const columns: ColumnDef<User>[] = [
|
|
{
|
|
id: "select",
|
|
header: ({ table }) => (
|
|
<Checkbox
|
|
checked={
|
|
table.getIsAllPageRowsSelected() ||
|
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
|
}
|
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
aria-label="انتخاب همه"
|
|
/>
|
|
),
|
|
cell: ({ row }) => (
|
|
<Checkbox
|
|
checked={row.getIsSelected()}
|
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
aria-label="انتخاب ردیف"
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
header: "نام",
|
|
accessorKey: "firstName",
|
|
cell: ({ row }) => {
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
{row.original.image ? (
|
|
<img
|
|
src={row.original.image}
|
|
alt={row.original.firstName ?? row.original.lastName}
|
|
className="size-6 rounded-md"
|
|
/>
|
|
) : (
|
|
<LetterAvatar>
|
|
{(row.original.firstName ?? row.original.lastName)
|
|
?.charAt(0)
|
|
.toUpperCase()}
|
|
</LetterAvatar>
|
|
)}
|
|
|
|
<span className="font-medium">
|
|
{row.original.firstName} {row.original.lastName}
|
|
</span>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
header: "شرکت",
|
|
cell: ({ row }) =>
|
|
row.original.company && (
|
|
<div className="flex items-center gap-2">
|
|
<LetterAvatar>
|
|
{row.original.company?.charAt(0).toUpperCase()}
|
|
</LetterAvatar>
|
|
<p>{row.original.company}</p>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
header: "نقش",
|
|
accessorKey: "jobTitle",
|
|
},
|
|
{
|
|
header: "ایمیل",
|
|
cell: ({ row }) =>
|
|
row.original.email && (
|
|
<Badge variant="outline">{row.original.email}</Badge>
|
|
),
|
|
},
|
|
];
|
|
|
|
interface UserCardProps {
|
|
user: User;
|
|
variant?: "merged" | "selected";
|
|
onSelect?: (key: string, value: string) => void;
|
|
mergedUser?: User | null;
|
|
}
|
|
|
|
const UserCard = ({
|
|
user,
|
|
variant = "selected",
|
|
onSelect,
|
|
mergedUser,
|
|
}: UserCardProps) => {
|
|
const mergeableKeys = [
|
|
"firstName",
|
|
"lastName",
|
|
"jobTitle",
|
|
"email",
|
|
"company",
|
|
];
|
|
|
|
return (
|
|
<div className="w-full border bg-background">
|
|
<div className="flex items-center gap-2 border-b p-3">
|
|
<div
|
|
className={cn(
|
|
"flex size-10 items-center justify-center overflow-hidden rounded-full bg-muted",
|
|
variant === "merged" ? "text-base" : "text-xs",
|
|
)}
|
|
>
|
|
{user.image ? (
|
|
<img
|
|
src={user.image}
|
|
alt={user.firstName ?? user.lastName}
|
|
className="size-full"
|
|
/>
|
|
) : (
|
|
<span>
|
|
{(user.firstName ?? user.lastName)?.charAt(0).toUpperCase()}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm font-medium">
|
|
{user.firstName} {user.lastName}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-3 p-3">
|
|
{mergeableKeys.map((key) => {
|
|
const value = user[key as keyof User];
|
|
|
|
if (!value) return null;
|
|
|
|
const id = `${key}-${value}`;
|
|
|
|
if (variant === "selected") {
|
|
return (
|
|
<RadioGroup key={id} className="flex items-center gap-2">
|
|
<RadioGroupItem
|
|
checked={mergedUser?.[key as keyof User] === value}
|
|
onClick={() => onSelect?.(key, value)}
|
|
value={id}
|
|
id={id}
|
|
/>
|
|
<Label htmlFor={id} className="font-normal">
|
|
{value}
|
|
</Label>
|
|
</RadioGroup>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div key={id} className="flex items-center gap-2 text-sm">
|
|
<Text className="size-4" />
|
|
{value}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface FieldMerging1Props {
|
|
className?: string;
|
|
}
|
|
|
|
const FieldMerging1 = ({ className }: FieldMerging1Props) => {
|
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
const [mergedUser, setMergedUser] = useState<User | null>(null);
|
|
|
|
const selectedCount = Object.keys(rowSelection).length;
|
|
|
|
useEffect(() => {
|
|
if (selectedCount === 0) {
|
|
setMergedUser(null);
|
|
} else {
|
|
setMergedUser(DUMMY_USERS[Number(Object.keys(rowSelection)[0])]);
|
|
}
|
|
}, [rowSelection, selectedCount]);
|
|
|
|
const table = useReactTable({
|
|
data: DUMMY_USERS,
|
|
columns,
|
|
getCoreRowModel: getCoreRowModel(),
|
|
onRowSelectionChange: setRowSelection,
|
|
state: {
|
|
rowSelection,
|
|
},
|
|
});
|
|
|
|
return (
|
|
<section className="py-32">
|
|
<div className={cn("container space-y-6", className)}>
|
|
<Dialog>
|
|
<DialogTrigger asChild disabled={selectedCount < 2}>
|
|
<Button>ادغام کاربران</Button>
|
|
</DialogTrigger>
|
|
<DialogContent
|
|
showCloseButton={false}
|
|
className="max-h-[95dvh] overflow-y-auto md:!max-w-3xl lg:!max-w-5xl"
|
|
>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
ترکیب {selectedCount.toLocaleString("fa-IR")} رکورد انتخابشده
|
|
</DialogTitle>
|
|
<DialogDescription className="sr-only">
|
|
از هر رکورد فیلدهای مورد نظر را انتخاب کنید تا یک ورودی یکپارچه
|
|
بسازید
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<div className="flex w-full flex-col-reverse items-start lg:flex-row">
|
|
<div className="grid w-full gap-3 bg-muted p-4 lg:flex-2/3 lg:grid-cols-2">
|
|
{Object.keys(rowSelection).map((key) => {
|
|
const user = DUMMY_USERS[Number(key)];
|
|
|
|
return (
|
|
<UserCard
|
|
key={`user-${key}`}
|
|
user={user}
|
|
onSelect={(fieldKey, value) =>
|
|
setMergedUser((prev) => ({ ...prev, [fieldKey]: value }))
|
|
}
|
|
mergedUser={mergedUser}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
{mergedUser && (
|
|
<div className="w-full space-y-1 border p-6 lg:flex-1/3">
|
|
<UserCard user={mergedUser} variant="merged" />
|
|
<Button className="w-full">ادغام</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Table>
|
|
<TableHeader>
|
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
<TableRow key={headerGroup.id}>
|
|
{headerGroup.headers.map((header) => (
|
|
<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"
|
|
>
|
|
رکوردی یافت نشد.
|
|
</TableCell>
|
|
</TableRow>
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { FieldMerging1 };
|