Files
reui-blocks/blocks/companies-create3.tsx
T

556 lines
16 KiB
TypeScript

"use client";
import {
Building2,
Calendar,
Check,
ChevronsUpDown,
Coins,
Facebook,
FileText,
Instagram,
Link,
Linkedin,
MapPin,
Tag,
TrendingUp,
Twitter,
User,
Users,
} from "lucide-react";
import { useState } from "react";
import {
Tags,
TagsContent,
TagsEmpty,
TagsGroup,
TagsInput,
TagsItem,
TagsList,
TagsTrigger,
TagsValue,
} from "@/components/kibo-ui/tags";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { cn } from "@/lib/utils";
interface ComboboxOption {
label: string;
value: string;
}
interface FormComboboxProps {
value: string;
onChange: (value: string) => void;
options: ComboboxOption[];
name?: string;
placeholder?: string;
searchPlaceholder?: string;
emptyMessage?: string;
required?: boolean;
}
interface FormTagsProps {
value: string[];
onChange: (value: string[]) => void;
options: ComboboxOption[];
name?: string;
searchPlaceholder?: string;
emptyMessage?: string;
required?: boolean;
}
const FormCombobox = ({
value,
onChange,
options,
name,
placeholder = "یک مورد را انتخاب کنید...",
searchPlaceholder = "جستجو بر اساس نام...",
emptyMessage = "موردی یافت نشد.",
required = false,
}: FormComboboxProps) => {
const [open, setOpen] = useState(false);
const selectedOption = options.find((option) => option.value === value);
const displayValue = selectedOption ? selectedOption.label : "";
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger className="w-full outline-none">
<div className="relative flex w-full items-center">
<Input
type="text"
name={name}
value={displayValue}
placeholder={placeholder}
required={required}
onChange={() => {}}
onClick={() => setOpen(true)}
className={cn(
"cursor-pointer bg-background pe-6",
!value && "text-muted-foreground",
)}
/>
<div className="pointer-events-auto absolute end-2 flex h-full items-center">
<ChevronsUpDown className="size-4 shrink-0 opacity-50" />
</div>
</div>
</PopoverTrigger>
<PopoverContent className="w-[300px] p-0" align="start">
<Command>
<CommandInput placeholder={searchPlaceholder} />
<CommandList>
<CommandEmpty>{emptyMessage}</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={(currentValue) => {
onChange(currentValue === value ? "" : currentValue);
setOpen(false);
}}
>
<Check
className={cn(
"me-2 size-4",
value === option.value ? "opacity-100" : "opacity-0",
)}
/>
{option.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
const FormTags = ({
value = [],
onChange,
options,
name,
searchPlaceholder = "جستجوی دسته‌بندی...",
emptyMessage = "دسته‌بندی‌ای یافت نشد.",
required = false,
}: FormTagsProps) => {
const [open, setOpen] = useState(false);
const handleSelect = (selectedValue: string) => {
const newValue = value.includes(selectedValue)
? value.filter((v) => v !== selectedValue)
: [...value, selectedValue];
onChange(newValue);
};
const handleRemove = (tagToRemove: string) => {
onChange(value.filter((v) => v !== tagToRemove));
};
return (
<Tags open={open} onOpenChange={setOpen}>
<TagsTrigger>
{value.length > 0
? value.map((tagValue) => {
const option = options.find((opt) => opt.value === tagValue);
return (
<TagsValue
key={tagValue}
onRemove={() => handleRemove(tagValue)}
>
{option?.label || tagValue}
</TagsValue>
);
})
: null}
</TagsTrigger>
<TagsContent>
<TagsInput placeholder={searchPlaceholder} />
<TagsList>
<TagsEmpty>{emptyMessage}</TagsEmpty>
<TagsGroup>
{options.map((option) => (
<TagsItem
key={option.value}
value={option.value}
onSelect={() => handleSelect(option.value)}
>
<Check
className={cn(
"me-2 size-4",
value.includes(option.value) ? "opacity-100" : "opacity-0",
)}
/>
{option.label}
</TagsItem>
))}
</TagsGroup>
</TagsList>
</TagsContent>
{required && value.length === 0 && (
<input
type="hidden"
name={name}
value=""
required
aria-invalid="true"
/>
)}
{value.map((tagValue) => (
<input key={tagValue} type="hidden" name={name} value={tagValue} />
))}
</Tags>
);
};
interface CompaniesCreate3Props {
title?: string;
className?: string;
}
const CompaniesCreate3 = ({
title = "ثبت شرکت",
className,
}: CompaniesCreate3Props) => {
const [formData, setFormData] = useState<Record<string, string | string[]>>(
{},
);
const [open, setOpen] = useState(false);
const companies = [
{
id: 1,
name: "شرکت آکمه",
description: "شرکتی فعال در توسعه راهکارهای نرم‌افزاری و خدمات دیجیتال",
location: "سانفرانسیسکو، آمریکا",
},
{
id: 2,
name: "تک‌استارت",
description: "فعال در حوزه فناوری، نوآوری و تحول دیجیتال",
location: "نیویورک، آمریکا",
},
];
const FIELDS = [
{
label: "نام رسمی شرکت",
name: "name",
placeholder: "مثال: شرکت فناوری نوآور",
icon: Building2,
required: true,
},
{
label: "معرفی شرکت",
name: "description",
placeholder: "معرفی کوتاهی از فعالیت‌ها، محصولات و خدمات شرکت بنویسید...",
icon: FileText,
},
{
label: "تیم",
name: "team",
placeholder: "اعضای مسئول این شرکت را انتخاب کنید...",
icon: User,
type: "combobox",
searchPlaceholder: "جستجوی اعضا...",
emptyMessage: "عضو تیمی یافت نشد.",
required: true,
options: [
{
label: "علی",
value: "alex",
},
{
label: "محمد",
value: "john",
},
{
label: "سارا",
value: "jane",
},
],
},
{
label: "دسته‌بندی‌ها",
name: "categories",
placeholder: "یک یا چند حوزه فعالیت را انتخاب کنید...",
icon: Tag,
type: "tags",
searchPlaceholder: "جستجوی دسته‌بندی...",
emptyMessage: "دسته‌بندی یافت نشد.",
options: [
{
label: "SaaS",
value: "saas",
},
{
label: "تجارت الکترونیک",
value: "ecommerce",
},
{
label: "فین‌تک",
value: "fintech",
},
{
label: "بهداشت و درمان",
value: "healthcare",
},
{
label: "آموزش",
value: "education",
},
{
label: "سازمانی",
value: "enterprise",
},
{
label: "B2B",
value: "b2b",
},
{
label: "B2C",
value: "b2c",
},
],
},
{
label: "موقعیت جغرافیایی",
name: "location",
placeholder: "مثال: تهران، ایران",
icon: MapPin,
},
{
label: "AngelList",
name: "angellist",
placeholder: "AngelList...",
icon: Link,
},
{
label: "Facebook",
name: "facebook",
placeholder: "Facebook...",
icon: Facebook,
},
{
label: "Instagram",
name: "instagram",
placeholder: "Instagram...",
icon: Instagram,
},
{
label: "LinkedIn",
name: "linkedin",
placeholder: "LinkedIn...",
icon: Linkedin,
},
{
label: "Twitter",
name: "twitter",
placeholder: "Twitter...",
icon: Twitter,
},
{
label: "برآورد درآمد تکرارشونده سالانه (ARR)",
name: "arr",
placeholder: "مثال: ۱۲۰٬۰۰۰ ریال",
icon: TrendingUp,
},
{
label: "سرمایه جذب‌شده",
name: "funding",
placeholder: "مثال: ۱۰ میلیون ریال",
icon: Coins,
},
{
label: "تاریخ تأسیس",
name: "founded_date",
placeholder: "مثال: ۱۳۹۸/۰۶/۱۵",
icon: Calendar,
},
{
label: "محدوده تعداد کارکنان",
name: "employee_range",
placeholder: "مثال: ۵۰ تا ۱۰۰ نفر",
icon: Users,
},
];
const handleInputChange = (name: string, value: string | string[]) => {
setFormData((prev) => ({ ...prev, [name]: value }));
};
return (
<div className="relative min-h-screen">
<div className="container py-32">
<div className="mb-8">
<h2 className="text-xl font-semibold tracking-tight">شرکت‌ها</h2>
<p className="mt-2 text-sm text-muted-foreground">
اطلاعات شرکت‌ها را ثبت، جستجو و مدیریت کنید.
</p>
</div>
<div className="mb-6 overflow-hidden rounded-lg border">
<Table>
<TableHeader>
<TableRow>
<TableHead>نام شرکت</TableHead>
<TableHead>معرفی شرکت</TableHead>
<TableHead>موقعیت جغرافیایی</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{companies.map((company) => (
<TableRow key={company.id}>
<TableCell className="font-medium">{company.name}</TableCell>
<TableCell>{company.description}</TableCell>
<TableCell>{company.location}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>ثبت شرکت</Button>
</DialogTrigger>
<DialogContent
className={cn(
"max-h-[90vh] max-w-2xl overflow-y-auto bg-muted",
className,
)}
>
<DialogHeader>
<DialogTitle className="text-xl font-semibold">
{title}
</DialogTitle>
<DialogDescription>
اطلاعات یک شرکت جدید را در سامانه ثبت کنید.
</DialogDescription>
</DialogHeader>
<form
autoComplete="off"
className="flex flex-col gap-2 pt-4"
onSubmit={(e) => {
e.preventDefault();
setOpen(false);
}}
>
{FIELDS.map((field, i) => {
const isCombobox = field.type === "combobox";
const isTags = field.type === "tags";
return (
<div
key={`companies-create-3-field-${i}-${field.label}`}
className="relative grid w-full grid-cols-4 gap-4"
>
<div className="col-span-2 flex h-full items-center gap-2 text-foreground/80 sm:col-span-1">
<field.icon className="size-4 shrink-0" />
<Label
htmlFor={field.name}
className={cn(
"min-w-0 truncate leading-loose",
field.required &&
"after:ms-1 after:text-destructive after:content-['*']",
)}
>
{field.label}
</Label>
</div>
<div className="col-span-2 shrink-0 sm:col-span-3">
{isCombobox && field.options ? (
<FormCombobox
name={field.name}
value={(formData[field.name] as string) || ""}
onChange={(value) =>
handleInputChange(field.name, value)
}
options={field.options}
placeholder={field.placeholder}
searchPlaceholder={field.searchPlaceholder}
emptyMessage={field.emptyMessage}
required={field.required}
/>
) : isTags && field.options ? (
<FormTags
name={field.name}
value={(formData[field.name] as string[]) || []}
onChange={(value) =>
handleInputChange(field.name, value)
}
options={field.options}
searchPlaceholder={field.searchPlaceholder}
emptyMessage={field.emptyMessage}
required={field.required}
/>
) : (
<Input
name={field.name}
placeholder={field.placeholder}
value={(formData[field.name] as string) || ""}
onChange={(e) =>
handleInputChange(field.name, e.target.value)
}
required={field.required}
className="bg-background"
/>
)}
</div>
</div>
);
})}
<div className="mt-6 flex justify-end gap-3 border-t pt-6">
<Button
type="button"
variant="outline"
onClick={() => setOpen(false)}
>
انصراف
</Button>
<Button type="submit">ثبت</Button>
</div>
</form>
</DialogContent>
</Dialog>
</div>
</div>
);
};
export { CompaniesCreate3 };