Files
reui-blocks/blocks/field-merging1.tsx
T

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 };