556 lines
16 KiB
TypeScript
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 };
|