313 lines
8.2 KiB
TypeScript
313 lines
8.2 KiB
TypeScript
"use client";
|
|
|
|
import { Check, MailIcon, MoreHorizontal, UserIcon } from "lucide-react";
|
|
import { useState } from "react";
|
|
|
|
import {
|
|
Tags,
|
|
TagsContent,
|
|
TagsEmpty,
|
|
TagsGroup,
|
|
TagsInput,
|
|
TagsItem,
|
|
TagsList,
|
|
TagsTrigger,
|
|
TagsValue,
|
|
} from "@/components/kibo-ui/tags";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface User {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
status?: "Pending" | "Accepted";
|
|
}
|
|
|
|
interface UsersTableProps {
|
|
users: User[];
|
|
}
|
|
|
|
const UsersTable = ({ users }: UsersTableProps) => {
|
|
const columns = [
|
|
{
|
|
label: "نام",
|
|
icon: UserIcon,
|
|
},
|
|
{
|
|
label: "ایمیل",
|
|
icon: MailIcon,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-2 text-start">
|
|
<p className="text-sm font-medium sm:text-base">کاربران دعوتشده</p>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
{columns.map((column, i) => (
|
|
<TableHead key={`col-${column.label}-${i}`}>
|
|
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<column.icon className="size-3.5" />
|
|
<span>{column.label}</span>
|
|
</div>
|
|
</TableHead>
|
|
))}
|
|
<TableHead />
|
|
<TableHead />
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{users.map((user) => (
|
|
<TableRow key={`user-${user.id}`}>
|
|
<TableCell>{user.name}</TableCell>
|
|
<TableCell dir="rtl" className="text-end">{user.email}</TableCell>
|
|
<TableCell>
|
|
<Badge
|
|
className={
|
|
user.status === "Accepted"
|
|
? "bg-green-100 text-green-800"
|
|
: "bg-yellow-100 text-yellow-800"
|
|
}
|
|
>
|
|
{user.status === "Accepted" ? "پذیرفته" : "در انتظار"}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon-sm">
|
|
<MoreHorizontal className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem variant="destructive">
|
|
لغو دسترسی
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface InviteUserFormProps {
|
|
value: string[];
|
|
options: {
|
|
value: string;
|
|
label: string;
|
|
}[];
|
|
onToggle: (value: string) => void;
|
|
}
|
|
|
|
const InviteUserForm = ({ value, options, onToggle }: InviteUserFormProps) => {
|
|
const [searchValue, setSearchValue] = useState("");
|
|
|
|
const handleCreateEmail = () => {
|
|
if (searchValue.trim() && !value.includes(searchValue.trim())) {
|
|
onToggle(searchValue.trim());
|
|
setSearchValue("");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form
|
|
autoComplete="off"
|
|
onSubmit={(e) => e.preventDefault()}
|
|
className="space-y-2 text-start"
|
|
>
|
|
<p className="text-sm font-medium sm:text-base">دعوت کاربر</p>
|
|
<div className="flex flex-wrap items-start gap-2 sm:flex-nowrap">
|
|
<Tags>
|
|
<TagsTrigger>
|
|
{value.map((tagValue) => {
|
|
const option = options.find((opt) => opt.value === tagValue);
|
|
return (
|
|
<TagsValue key={tagValue} onRemove={() => onToggle(tagValue)}>
|
|
{option?.label || tagValue}
|
|
</TagsValue>
|
|
);
|
|
})}
|
|
</TagsTrigger>
|
|
<TagsContent>
|
|
<TagsInput
|
|
placeholder="جستجوی کاربران..."
|
|
value={searchValue}
|
|
onValueChange={setSearchValue}
|
|
/>
|
|
<TagsList>
|
|
<TagsEmpty>
|
|
{searchValue.trim() && (
|
|
<Button
|
|
variant="ghost"
|
|
onClick={handleCreateEmail}
|
|
className="m-1 w-full justify-start"
|
|
>
|
|
ایجاد «{searchValue.trim()}»
|
|
</Button>
|
|
)}
|
|
</TagsEmpty>
|
|
|
|
<TagsGroup>
|
|
{options.map((option) => (
|
|
<TagsItem
|
|
key={option.value}
|
|
value={option.value}
|
|
onSelect={() => onToggle(option.value)}
|
|
className="justify-start"
|
|
>
|
|
<Check
|
|
className={cn(
|
|
"size-4 opacity-0",
|
|
value.includes(option.value) && "opacity-100",
|
|
)}
|
|
/>
|
|
{option.label}
|
|
</TagsItem>
|
|
))}
|
|
</TagsGroup>
|
|
</TagsList>
|
|
</TagsContent>
|
|
</Tags>
|
|
<Button
|
|
type="submit"
|
|
className="w-full sm:h-10 sm:w-auto"
|
|
disabled={value.length === 0}
|
|
>
|
|
دعوت کاربران
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
);
|
|
};
|
|
|
|
interface InviteUser3Props {
|
|
heading?: string;
|
|
description?: string;
|
|
companyLogo?: string;
|
|
className?: string;
|
|
nonInvitedConnections?: User[];
|
|
invitedUsers?: User[];
|
|
}
|
|
|
|
const InviteUser3 = ({
|
|
heading = "افراد را به فضای کار خود دعوت کنید",
|
|
description =
|
|
"برای آنها ایمیل دعوت ارسال میکنیم تا به فضای کار شما بپیوندند.",
|
|
companyLogo = "/block-media/shadcnblocks/block/logos/ShadcnBlocks-logo-word.png",
|
|
className,
|
|
nonInvitedConnections = [
|
|
{
|
|
id: 3,
|
|
name: "آلیس اسمیت",
|
|
email: "alice.smith@example.com",
|
|
},
|
|
{
|
|
id: 5,
|
|
name: "چارلی براون",
|
|
email: "charlie.brown@example.com",
|
|
},
|
|
{
|
|
id: 13,
|
|
name: "کوین هارت",
|
|
email: "kevin.hart@example.com",
|
|
},
|
|
{
|
|
id: 15,
|
|
name: "مایکل اسکات",
|
|
email: "michael.scott@example.com",
|
|
},
|
|
{
|
|
id: 17,
|
|
name: "اسکار ویلد",
|
|
email: "oscar.wilde@example.com",
|
|
},
|
|
],
|
|
invitedUsers = [
|
|
{
|
|
id: 1,
|
|
name: "جین دو",
|
|
email: "jane.doe@example.com",
|
|
status: "Pending",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "جان دو",
|
|
email: "john.doe@example.com",
|
|
status: "Accepted",
|
|
},
|
|
{
|
|
id: 6,
|
|
name: "دایانا روس",
|
|
email: "diana.ross@example.com",
|
|
status: "Accepted",
|
|
},
|
|
],
|
|
}: InviteUser3Props) => {
|
|
const [value, setValue] = useState<string[]>([]);
|
|
|
|
const options = nonInvitedConnections.map((user) => ({
|
|
value: user.email,
|
|
label: user.name,
|
|
}));
|
|
|
|
const handleToggle = (selectedValue: string) => {
|
|
setValue((prev) =>
|
|
prev.includes(selectedValue)
|
|
? prev.filter((v) => v !== selectedValue)
|
|
: [...prev, selectedValue],
|
|
);
|
|
};
|
|
|
|
return (
|
|
<section className="py-32">
|
|
<div className="container">
|
|
<div className={cn("mx-auto max-w-3xl space-y-12", className)}>
|
|
<img
|
|
src={companyLogo}
|
|
alt="لوگوی شرکت"
|
|
className="w-56 sm:w-68 dark:invert"
|
|
/>
|
|
|
|
<div className="text-start">
|
|
<p className="text-xl font-medium sm:text-xl">{heading}</p>
|
|
<p className="text-sm text-muted-foreground sm:text-base">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-8">
|
|
<InviteUserForm
|
|
value={value}
|
|
options={options}
|
|
onToggle={handleToggle}
|
|
/>
|
|
<UsersTable users={invitedUsers} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { InviteUser3 };
|