"use client" import * as React from "react" import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" // react-hook-form + shadcn/ui Form import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Role, userRoles } from "@/db/schema/users" import { createUserSchema, type CreateUserSchema } from "@/lib/admin-users/validations" import { createAdminUser, getAllCompanies, getAllRoles } from "@/lib/admin-users/service" import { type Company } from "@/db/schema/companies" import { MultiSelect } from "@/components/ui/multi-select" import { Popover, PopoverTrigger, PopoverContent, } from "@/components/ui/popover" import { Command, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, } from "@/components/ui/command" import { Check, ChevronsUpDown, Loader } from "lucide-react" import { cn } from "@/lib/utils" import { toast } from "sonner" import { Vendor } from "@/db/schema/vendors" import { FormDescription } from "@/components/ui/form" import { ScrollArea } from "@/components/ui/scroll-area" // i18n-iso-countries import import i18nIsoCountries from "i18n-iso-countries" import enLocale from "i18n-iso-countries/langs/en.json" import koLocale from "i18n-iso-countries/langs/ko.json" i18nIsoCountries.registerLocale(enLocale) i18nIsoCountries.registerLocale(koLocale) const locale = "ko" const countryMap = i18nIsoCountries.getNames(locale, { select: "official" }) const countryArray = Object.entries(countryMap).map(([code, label]) => ({ code, label, })) // Sort countries to put Korea first, then alphabetically const sortedCountryArray = [...countryArray].sort((a, b) => { if (a.code === "KR") return -1; if (b.code === "KR") return 1; return a.label.localeCompare(b.label); }); // Add English names for Korean locale const enhancedCountryArray = sortedCountryArray.map(country => ({ ...country, label: locale === "ko" && country.code === "KR" ? "대한민국 (South Korea)" : country.label })); // Comprehensive list of country dial codes const countryDialCodes: { [key: string]: string } = { AF: "+93", AL: "+355", DZ: "+213", AS: "+1-684", AD: "+376", AO: "+244", AI: "+1-264", AG: "+1-268", AR: "+54", AM: "+374", AW: "+297", AU: "+61", AT: "+43", AZ: "+994", BS: "+1-242", BH: "+973", BD: "+880", BB: "+1-246", BY: "+375", BE: "+32", BZ: "+501", BJ: "+229", BM: "+1-441", BT: "+975", BO: "+591", BA: "+387", BW: "+267", BR: "+55", BN: "+673", BG: "+359", BF: "+226", BI: "+257", KH: "+855", CM: "+237", CA: "+1", CV: "+238", KY: "+1-345", CF: "+236", TD: "+235", CL: "+56", CN: "+86", CO: "+57", KM: "+269", CG: "+242", CD: "+243", CR: "+506", CI: "+225", HR: "+385", CU: "+53", CY: "+357", CZ: "+420", DK: "+45", DJ: "+253", DM: "+1-767", DO: "+1-809", EC: "+593", EG: "+20", SV: "+503", GQ: "+240", ER: "+291", EE: "+372", ET: "+251", FJ: "+679", FI: "+358", FR: "+33", GA: "+241", GM: "+220", GE: "+995", DE: "+49", GH: "+233", GR: "+30", GD: "+1-473", GT: "+502", GN: "+224", GW: "+245", GY: "+592", HT: "+509", HN: "+504", HK: "+852", HU: "+36", IS: "+354", IN: "+91", ID: "+62", IR: "+98", IQ: "+964", IE: "+353", IL: "+972", IT: "+39", JM: "+1-876", JP: "+81", JO: "+962", KZ: "+7", KE: "+254", KI: "+686", KR: "+82", KW: "+965", KG: "+996", LA: "+856", LV: "+371", LB: "+961", LS: "+266", LR: "+231", LY: "+218", LI: "+423", LT: "+370", LU: "+352", MK: "+389", MG: "+261", MW: "+265", MY: "+60", MV: "+960", ML: "+223", MT: "+356", MH: "+692", MR: "+222", MU: "+230", MX: "+52", FM: "+691", MD: "+373", MC: "+377", MN: "+976", ME: "+382", MA: "+212", MZ: "+258", MM: "+95", NA: "+264", NR: "+674", NP: "+977", NL: "+31", NZ: "+64", NI: "+505", NE: "+227", NG: "+234", NU: "+683", KP: "+850", NO: "+47", OM: "+968", PK: "+92", PW: "+680", PS: "+970", PA: "+507", PG: "+675", PY: "+595", PE: "+51", PH: "+63", PL: "+48", PT: "+351", PR: "+1-787", QA: "+974", RO: "+40", RU: "+7", RW: "+250", KN: "+1-869", LC: "+1-758", VC: "+1-784", WS: "+685", SM: "+378", ST: "+239", SA: "+966", SN: "+221", RS: "+381", SC: "+248", SL: "+232", SG: "+65", SK: "+421", SI: "+386", SB: "+677", SO: "+252", ZA: "+27", SS: "+211", ES: "+34", LK: "+94", SD: "+249", SR: "+597", SZ: "+268", SE: "+46", CH: "+41", SY: "+963", TW: "+886", TJ: "+992", TZ: "+255", TH: "+66", TL: "+670", TG: "+228", TK: "+690", TO: "+676", TT: "+1-868", TN: "+216", TR: "+90", TM: "+993", TV: "+688", UG: "+256", UA: "+380", AE: "+971", GB: "+44", US: "+1", UY: "+598", UZ: "+998", VU: "+678", VA: "+39-06", VE: "+58", VN: "+84", YE: "+967", ZM: "+260", ZW: "+263" }; const languageOptions = [ { value: "ko", label: "한국어" }, { value: "en", label: "English" }, ] // Phone validation helper const validatePhoneNumber = (phone: string): boolean => { if (!phone) return true; // Optional field // Basic international phone number validation const cleanPhone = phone.replace(/[\s\-\(\)]/g, ''); return /^\+\d{3,20}$/.test(cleanPhone); }; // Get phone placeholder const getPhonePlaceholder = (countryCode: string | undefined) => { if (!countryCode || !countryDialCodes[countryCode]) return "전화번호"; return `${countryDialCodes[countryCode]} 전화번호`; }; // Get phone description const getPhoneDescription = (): string => { return "국제 전화번호를 입력하세요. (예: +82-10-1234-5678)"; }; export function AddUserDialog() { const [open, setOpen] = React.useState(false) const [companies, setCompanies] = React.useState([]) // 회사 목록 const [roles, setRoles] = React.useState([]) const [isAddPending, startAddTransition] = React.useTransition() React.useEffect(() => { // 회사 목록 불러오기 (예시) getAllCompanies().then((res) => { setCompanies(res) }) getAllRoles().then((res) => { setRoles(res) }) }, []) // react-hook-form 세팅 const form = useForm({ resolver: zodResolver(createUserSchema), defaultValues: { name: "", email: "", phone: "", // Add phone field companyId: null, language:'en', // roles는 array, 여기서는 단일 선택 시 [role]로 담음 roles: ["Vendor Admin"], domain:'partners' // domain, etc. 필요하다면 추가 }, }) async function onSubmit(data: CreateUserSchema & { language?: string; phone?: string }) { data.domain = "partners" // Phone validation is now handled by zod schema // 만약 단일 Select로 role을 정했다면, data.roles = ["manager"] 이런 식 startAddTransition(async ()=> { const result = await createAdminUser(data) if (result.error) { toast.error(`에러: ${result.error}`) return } form.reset() setOpen(false) toast.success("User added") }) } function handleDialogOpenChange(nextOpen: boolean) { if (!nextOpen) { form.reset() } setOpen(nextOpen) } return ( {/* 모달을 열기 위한 버튼 */} Create New User 새 User 정보를 입력하고 Create 버튼을 누르세요. {/* shadcn/ui Form을 이용해 react-hook-form과 연결 */}
{/* 사용자 이름 */} ( User Name )} /> {/* 이메일 */} ( Email )} /> {/* 국가 선택 */} ( Country 국가를 찾을 수 없습니다. {Object.entries(i18nIsoCountries.getNames("ko")).map(([code, name]) => ( { form.setValue("country", code); }} > {name} ))} )} /> {/* 전화번호 - 새로 추가 */} ( Phone Number {getPhoneDescription()} )} /> {/* 회사 선택 (companyId) */} { // 현재 선택된 회사 ID (number) → 문자열 const valueString = field.value ? String(field.value) : "" // 현재 선택된 회사 const selectedCompany = companies.find( (c) => String(c.id) === valueString ) const selectedCompanyLabel = selectedCompany && `${selectedCompany.vendorName} ${selectedCompany.taxId}` const [popoverOpen, setPopoverOpen] = React.useState(false) return ( Company No company found. {companies.map((comp) => { // string(comp.id) const compIdStr = String(comp.id) const label = `${comp.vendorName}${comp.taxId}` const label2 = `${comp.vendorName} ${comp.taxId}` return ( { // 회사 ID를 number로 field.onChange(Number(comp.id)) setPopoverOpen(false) }} > {label2} ) })} ) }} /> {/* Role (Vendor Admin) - 읽기 전용 */} ( Role {/* UI에선 그냥 Vendor Admin이라고 표시만 (disabled) */} )} /> {/* language Select */} ( Language )} />
) }