284 lines
9.8 KiB
TypeScript
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 };
|