Files
reui-blocks/blocks/settings-members4.tsx
T

284 lines
9.8 KiB
TypeScript

"use client";
import { Trash2, UserPlus } from "lucide-react";
import { useMemo, useState } from "react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { cn } from "@/lib/utils";
interface Member {
id: string;
name: string;
email: string;
role: string;
avatar?: string;
status: "active" | "pending";
}
interface SettingsMembers4Props {
heading?: string;
description?: string;
members?: Member[];
className?: string;
}
const SettingsMembers4 = ({
heading = "اعضای سازمان",
description = "کنترل کنید چه کسانی به داده‌ها و پایپ‌لاین سازمان دسترسی دارند.",
members = [
{
id: "1",
name: "نینا کاویانی",
email: "nina.k@startup.co",
role: "مالک",
avatar:
"/block-media/shadcnblocks/block/avatar/avatar2.jpg",
status: "active",
},
{
id: "2",
name: "جمشید لیوایی",
email: "james.l@startup.co",
role: "مدیر عملیات",
avatar:
"/block-media/shadcnblocks/block/avatar/avatar3.jpg",
status: "active",
},
{
id: "3",
name: "راحله سبزواری",
email: "rachel.g@startup.co",
role: "کارشناس فروش",
avatar:
"/block-media/shadcnblocks/block/avatar/avatar9.jpg",
status: "active",
},
{
id: "4",
name: "عمران حسنی",
email: "omar.h@startup.co",
role: "کارشناس فروش",
status: "pending",
},
{
id: "5",
name: "لیلا وهابی",
email: "lisa.w@startup.co",
role: "عملیات بازاریابی",
avatar:
"/block-media/shadcnblocks/block/avatar/avatar10.jpg",
status: "active",
},
],
className,
}: SettingsMembers4Props) => {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [isInviteOpen, setIsInviteOpen] = useState(false);
const allSelected = selectedIds.length === members.length;
const someSelected = selectedIds.length > 0 && !allSelected;
const toggleAll = () => {
if (allSelected) {
setSelectedIds([]);
} else {
setSelectedIds(members.map((m) => m.id));
}
};
const toggleMember = (id: string) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id],
);
};
const selectedCount = selectedIds.length;
const roleStats = useMemo(() => {
const stats: Record<string, number> = {};
members.forEach((m) => {
stats[m.role] = (stats[m.role] || 0) + 1;
});
return stats;
}, [members]);
return (
<section dir="rtl" lang="fa" className={cn("w-full py-32", className)}>
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="flex w-full flex-col gap-6">
<div className="space-y-1">
<h2 className="text-xl font-semibold tracking-tight">{heading}</h2>
<p className="text-sm text-muted-foreground">{description}</p>
</div>
<div className="flex flex-wrap items-center gap-2">
{Object.entries(roleStats).map(([role, count]) => (
<Badge key={role} variant="outline" className="font-normal">
{role}: {count.toLocaleString("fa-IR")}
</Badge>
))}
</div>
<div className="rounded-lg border">
<div className="flex items-center justify-between border-b bg-muted/50 px-4 py-3">
<div className="flex items-center gap-3">
<Checkbox
checked={allSelected}
onCheckedChange={toggleAll}
aria-label="انتخاب همه اعضا"
{...(someSelected && { "data-state": "indeterminate" })}
/>
<span className="text-sm font-medium">
{selectedCount > 0
? `${selectedCount.toLocaleString("fa-IR")} عضو انتخاب‌شده`
: `${members.length.toLocaleString("fa-IR")} عضو سازمان`}
</span>
</div>
<div className="flex items-center gap-2">
{selectedCount > 0 && (
<Button
variant="outline"
size="sm"
className="text-destructive hover:bg-destructive/10"
onClick={() => setSelectedIds([])}
>
<Trash2 className="size-4" />
حذف انتخاب‌شده‌ها
</Button>
)}
<Dialog open={isInviteOpen} onOpenChange={setIsInviteOpen}>
<DialogTrigger asChild>
<Button size="sm">
<UserPlus className="size-4" />
دعوت به سازمان
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>دعوت به سازمان</DialogTitle>
<DialogDescription>
عضو جدیدی با نقش مشخص به سازمان دعوت کنید.
</DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
setIsInviteOpen(false);
}}
className="space-y-4"
>
<div className="space-y-2">
<Label htmlFor="invite-email">ایمیل سازمانی</Label>
<Input
id="invite-email"
type="email"
placeholder="name@company.com"
/>
</div>
<div className="space-y-2">
<Label htmlFor="invite-role">نقش سازمانی</Label>
<Select defaultValue="developer">
<SelectTrigger id="invite-role">
<SelectValue placeholder="انتخاب نقش" />
</SelectTrigger>
<SelectContent>
<SelectItem value="admin">مدیر عملیات</SelectItem>
<SelectItem value="developer">کارشناس فروش</SelectItem>
<SelectItem value="designer">عملیات بازاریابی</SelectItem>
<SelectItem value="viewer">فقط مشاهده</SelectItem>
</SelectContent>
</Select>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setIsInviteOpen(false)}
>
انصراف
</Button>
<Button type="submit">ارسال دعوتنامه</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
</div>
<ul className="divide-y">
{members.map((member) => (
<li
key={member.id}
className={cn(
"flex items-center gap-4 px-4 py-3 transition-colors",
selectedIds.includes(member.id) && "bg-muted/30",
)}
>
<Checkbox
checked={selectedIds.includes(member.id)}
onCheckedChange={() => toggleMember(member.id)}
aria-label={`انتخاب ${member.name}`}
/>
<Avatar className="size-9">
<AvatarImage src={member.avatar} alt={`آواتار ${member.name}`} />
<AvatarFallback className="text-xs">
{member.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<p className="truncate text-sm font-medium">
{member.name}
</p>
{member.status === "pending" && (
<Badge
variant="secondary"
className="bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
>
دعوت در انتظار
</Badge>
)}
</div>
<p className="truncate text-xs text-muted-foreground">
{member.email}
</p>
</div>
<Badge variant="outline" className="shrink-0 font-normal">
{member.role}
</Badge>
</li>
))}
</ul>
</div>
</div>
</div>
</section>
);
};
export { SettingsMembers4 };