diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-15 12:52:11 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-15 12:52:11 +0000 |
| commit | b54f6f03150dd78d86db62201b6386bf14b72394 (patch) | |
| tree | b3092bb34805fdc65eee5282e86a9fb90ba20d6e /lib/owner-companies | |
| parent | c1bd1a2f499ee2f0742170021b37dab410983ab7 (diff) | |
(대표님) 커버, 데이터룸, 파일매니저, 담당자할당 등
Diffstat (limited to 'lib/owner-companies')
| -rw-r--r-- | lib/owner-companies/owner-company-form.tsx | 99 | ||||
| -rw-r--r-- | lib/owner-companies/owner-company-list.tsx | 85 | ||||
| -rw-r--r-- | lib/owner-companies/owner-company-user-form.tsx | 125 | ||||
| -rw-r--r-- | lib/owner-companies/owner-company-user-list.tsx | 93 | ||||
| -rw-r--r-- | lib/owner-companies/service.ts | 77 |
5 files changed, 479 insertions, 0 deletions
diff --git a/lib/owner-companies/owner-company-form.tsx b/lib/owner-companies/owner-company-form.tsx new file mode 100644 index 00000000..a385eccc --- /dev/null +++ b/lib/owner-companies/owner-company-form.tsx @@ -0,0 +1,99 @@ +// app/(admin)/owner-companies/_components/owner-company-form.tsx +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { createOwnerCompany, updateOwnerCompany } from "./service"; + +const formSchema = z.object({ + name: z.string().min(1, "회사명을 입력해주세요"), +}); + +type FormValues = z.infer<typeof formSchema>; + +interface OwnerCompanyFormProps { + initialData?: { + id: number; + name: string; + }; +} + +export function OwnerCompanyForm({ initialData }: OwnerCompanyFormProps) { + const router = useRouter(); + const isEdit = !!initialData; + + const form = useForm<FormValues>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: initialData?.name || "", + }, + }); + + async function onSubmit(values: FormValues) { + try { + const result = isEdit + ? await updateOwnerCompany(initialData.id, values) + : await createOwnerCompany(values); + + if (result.success) { + toast.success( + isEdit ? "회사 정보가 수정되었습니다" : "회사가 등록되었습니다" + ); + router.push("/evcp/data-room/owner-companies"); + router.refresh(); + } + } catch (error) { + toast.error("오류가 발생했습니다"); + } + } + + return ( + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> + <FormField + control={form.control} + name="name" + render={({ field }) => ( + <FormItem> + <FormLabel>회사명 *</FormLabel> + <FormControl> + <Input placeholder="삼성전자" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <div className="flex gap-2"> + <Button + type="button" + variant="outline" + onClick={() => router.back()} + > + 취소 + </Button> + <Button type="submit" disabled={form.formState.isSubmitting}> + {form.formState.isSubmitting + ? "처리 중..." + : isEdit + ? "수정" + : "등록"} + </Button> + </div> + </form> + </Form> + ); +}
\ No newline at end of file diff --git a/lib/owner-companies/owner-company-list.tsx b/lib/owner-companies/owner-company-list.tsx new file mode 100644 index 00000000..b78b193b --- /dev/null +++ b/lib/owner-companies/owner-company-list.tsx @@ -0,0 +1,85 @@ +// app/(admin)/owner-companies/_components/owner-company-list.tsx +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import Link from "next/link"; +import { Building2, Users } from "lucide-react"; + +interface OwnerCompany { + id: number; + name: string; + createdAt: Date; +} + +interface OwnerCompanyListProps { + companies: OwnerCompany[]; +} + +export function OwnerCompanyList({ companies }: OwnerCompanyListProps) { + if (companies.length === 0) { + return ( + <div className="text-center py-12"> + <Building2 className="mx-auto h-12 w-12 text-muted-foreground" /> + <h3 className="mt-4 text-lg font-semibold">등록된 회사가 없습니다</h3> + <p className="mt-2 text-sm text-muted-foreground"> + 첫 번째 발주처 회사를 등록해보세요. + </p> + <Button asChild className="mt-4"> + <Link href="/evcp/data-room/owner-companies/new">회사 등록</Link> + </Button> + </div> + ); + } + + return ( + <Table> + <TableHeader> + <TableRow> + <TableHead>회사명</TableHead> + <TableHead>등록일</TableHead> + <TableHead className="text-right">작업</TableHead> + </TableRow> + </TableHeader> + <TableBody> + {companies.map((company) => ( + <TableRow key={company.id}> + <TableCell className="font-medium"> + <div className="flex items-center gap-2"> + <Building2 className="h-4 w-4 text-muted-foreground" /> + {company.name} + </div> + </TableCell> + <TableCell> + {new Date(company.createdAt).toLocaleDateString("ko-KR", { + year: "numeric", + month: "long", + day: "numeric", + })} + </TableCell> + <TableCell className="text-right"> + <div className="flex gap-2 justify-end"> + <Button variant="outline" size="sm" asChild> + <Link href={`/owner-companies/${company.id}`}>수정</Link> + </Button> + <Button variant="outline" size="sm" asChild> + <Link href={`/evcp/data-room/owner-companies/${company.id}/users`}> + <Users className="h-4 w-4 mr-1" /> + 사용자 관리 + </Link> + </Button> + </div> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); +}
\ No newline at end of file diff --git a/lib/owner-companies/owner-company-user-form.tsx b/lib/owner-companies/owner-company-user-form.tsx new file mode 100644 index 00000000..52253607 --- /dev/null +++ b/lib/owner-companies/owner-company-user-form.tsx @@ -0,0 +1,125 @@ +// app/(admin)/owner-companies/_components/owner-company-user-form.tsx +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { createOwnerCompanyUser } from "./service"; + +const formSchema = z.object({ + name: z.string().min(1, "이름을 입력해주세요"), + email: z.string().email("올바른 이메일을 입력해주세요"), + phone: z.string().optional(), +}); + +type FormValues = z.infer<typeof formSchema>; + +interface OwnerCompanyUserFormProps { + companyId: number; +} + +export function OwnerCompanyUserForm({ companyId }: OwnerCompanyUserFormProps) { + const router = useRouter(); + + const form = useForm<FormValues>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + email: "", + phone: "", + }, + }); + + async function onSubmit(values: FormValues) { + try { + const result = await createOwnerCompanyUser(companyId, values); + + if (result.success) { + toast.success("사용자가 등록되었습니다"); + router.push(`/evcp/data-room/owner-companies/${companyId}/users`); + router.refresh(); + } else { + toast.error(result.error || "오류가 발생했습니다"); + } + } catch (error) { + toast.error("오류가 발생했습니다"); + } + } + + return ( + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> + <FormField + control={form.control} + name="name" + render={({ field }) => ( + <FormItem> + <FormLabel>이름 *</FormLabel> + <FormControl> + <Input placeholder="홍길동" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="email" + render={({ field }) => ( + <FormItem> + <FormLabel>이메일 *</FormLabel> + <FormControl> + <Input + type="email" + placeholder="user@company.com" + {...field} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <FormField + control={form.control} + name="phone" + render={({ field }) => ( + <FormItem> + <FormLabel>전화번호</FormLabel> + <FormControl> + <Input placeholder="+82-10-1234-5678" {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + <div className="flex gap-2"> + <Button + type="button" + variant="outline" + onClick={() => router.back()} + > + 취소 + </Button> + <Button type="submit" disabled={form.formState.isSubmitting}> + {form.formState.isSubmitting ? "처리 중..." : "등록"} + </Button> + </div> + </form> + </Form> + ); +}
\ No newline at end of file diff --git a/lib/owner-companies/owner-company-user-list.tsx b/lib/owner-companies/owner-company-user-list.tsx new file mode 100644 index 00000000..1f0963fe --- /dev/null +++ b/lib/owner-companies/owner-company-user-list.tsx @@ -0,0 +1,93 @@ +// app/(admin)/owner-companies/_components/owner-company-user-list.tsx +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import { UserPlus } from "lucide-react"; +import Link from "next/link"; + +interface User { + id: number; + name: string; + email: string; + phone: string | null; + isActive: boolean; + createdAt: Date; + employeeNumber: string | null; +} + +interface OwnerCompanyUserListProps { + users: User[]; + companyId: number; +} + +export function OwnerCompanyUserList({ + users, + companyId, +}: OwnerCompanyUserListProps) { + if (users.length === 0) { + return ( + <div className="text-center py-12 border rounded-lg"> + <UserPlus className="mx-auto h-12 w-12 text-muted-foreground" /> + <h3 className="mt-4 text-lg font-semibold">등록된 사용자가 없습니다</h3> + <p className="mt-2 text-sm text-muted-foreground"> + 첫 번째 사용자를 추가해보세요. + </p> + <Button asChild className="mt-4"> + <Link href={`/evcp/data-room/owner-companies/${companyId}/users/new`}> + 사용자 추가 + </Link> + </Button> + </div> + ); + } + + return ( + <Table> + <TableHeader> + <TableRow> + <TableHead>이름</TableHead> + <TableHead>이메일</TableHead> + <TableHead>전화번호</TableHead> + <TableHead>사번</TableHead> + <TableHead>상태</TableHead> + <TableHead>등록일</TableHead> + </TableRow> + </TableHeader> + <TableBody> + {users.map((user) => ( + <TableRow key={user.id}> + <TableCell className="font-medium">{user.name}</TableCell> + <TableCell>{user.email}</TableCell> + <TableCell>{user.phone || "-"}</TableCell> + <TableCell>{user.employeeNumber || "-"}</TableCell> + <TableCell> + {user.isActive ? ( + <Badge variant="default" className="bg-green-500"> + 활성 + </Badge> + ) : ( + <Badge variant="destructive">비활성</Badge> + )} + </TableCell> + <TableCell> + {new Date(user.createdAt).toLocaleDateString("ko-KR", { + year: "numeric", + month: "2-digit", + day: "2-digit", + })} + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); +}
\ No newline at end of file diff --git a/lib/owner-companies/service.ts b/lib/owner-companies/service.ts new file mode 100644 index 00000000..3692abd4 --- /dev/null +++ b/lib/owner-companies/service.ts @@ -0,0 +1,77 @@ +// lib/owner-companies/service.ts +"use server"; + +import db from "@/db/db"; +import { ownerCompanies, users } from "@/db/schema"; +import { revalidatePath } from "next/cache"; +import { eq } from "drizzle-orm"; + +export async function createOwnerCompany(data: { name: string }) { + const [company] = await db + .insert(ownerCompanies) + .values({ + name: data.name, + }) + .returning(); + + revalidatePath("/owner-companies"); + return { success: true, data: company }; +} + +export async function updateOwnerCompany( + id: number, + data: { name: string } +) { + const [company] = await db + .update(ownerCompanies) + .set({ + name: data.name, + }) + .where(eq(ownerCompanies.id, id)) + .returning(); + + revalidatePath("/owner-companies"); + revalidatePath(`/owner-companies/${id}`); + return { success: true, data: company }; +} + +export async function createOwnerCompanyUser( + companyId: number, + data: { + name: string; + email: string; + phone?: string; + employeeNumber?: string; + } +) { + // 이메일 중복 체크 + const existing = await db + .select() + .from(users) + .where(eq(users.email, data.email)) + .limit(1); + + if (existing.length > 0) { + return { success: false, error: "이미 사용 중인 이메일입니다." }; + } + + const [user] = await db + .insert(users) + .values({ + ...data, + ownerCompanyId: companyId, + domain: "owner", // 발주처 도메인 + isActive: true, + }) + .returning(); + + revalidatePath(`/owner-companies/${companyId}/users`); + return { success: true, data: user }; +} + +export async function getOwnerCompanyUsers(companyId: number) { + return await db + .select() + .from(users) + .where(eq(users.ownerCompanyId, companyId)); +}
\ No newline at end of file |
