고객사 하는중중
This commit is contained in:
parent
7b90ebd0d1
commit
68101a1811
369
plm-app/src/app/(admin)/common/oemmng/components/UserForm.tsx
Normal file
369
plm-app/src/app/(admin)/common/oemmng/components/UserForm.tsx
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import * as z from "zod";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
// import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { api } from "@/lib/api";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { User, Role } from "@/types/user";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
|
||||||
|
// Define the form schema with Zod
|
||||||
|
const formSchema = z.object({
|
||||||
|
username: z.string().min(3, "아이디는 3자 이상이어야 합니다"),
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine((val) => !val || val.length >= 8, {
|
||||||
|
message: "비밀번호는 8자 이상이어야 합니다",
|
||||||
|
}),
|
||||||
|
name: z.string().min(2, "이름은 2자 이상이어야 합니다"),
|
||||||
|
email: z.string().email("올바른 이메일 형식이 아닙니다"),
|
||||||
|
phone: z.string().regex(/^[0-9-]+$/, "올바른 전화번호 형식이 아닙니다"),
|
||||||
|
role: z.enum(["company_admin", "branch_admin", "user"], {
|
||||||
|
required_error: "역할을 선택해주세요",
|
||||||
|
}),
|
||||||
|
roleId: z.string().uuid("올바른 권한 그룹을 선택해주세요"),
|
||||||
|
isActive: z.boolean(),
|
||||||
|
branchId: z.string().uuid("올바른 지점을 선택해주세요"),
|
||||||
|
departmentId: z.string().uuid("올바른 부서를 선택해주세요"),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormSchema = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
interface UserFormProps {
|
||||||
|
initialData?: Partial<User>;
|
||||||
|
onSubmit: (data: Partial<User>) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserForm: React.FC<UserFormProps> = ({
|
||||||
|
initialData,
|
||||||
|
onSubmit,
|
||||||
|
onCancel,
|
||||||
|
}) => {
|
||||||
|
const { token, user } = useAuthStore();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
// Initialize the form with react-hook-form and zod resolver
|
||||||
|
const form = useForm<FormSchema>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
username: initialData?.username || "",
|
||||||
|
password: initialData?.password || "",
|
||||||
|
name: initialData?.name || "",
|
||||||
|
email: initialData?.email || "",
|
||||||
|
phone: initialData?.phone || "",
|
||||||
|
role:
|
||||||
|
(initialData?.role as "company_admin" | "branch_admin" | "user") ||
|
||||||
|
"user",
|
||||||
|
roleId: initialData?.Roles?.[0]?.id || "",
|
||||||
|
isActive: initialData?.isActive || false,
|
||||||
|
branchId: initialData?.branchId || "",
|
||||||
|
departmentId: initialData?.departmentId || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset form when initialData changes
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (initialData) {
|
||||||
|
form.reset({
|
||||||
|
username: initialData.username || "",
|
||||||
|
password: initialData.password || "",
|
||||||
|
name: initialData.name || "",
|
||||||
|
email: initialData.email || "",
|
||||||
|
phone: initialData.phone || "",
|
||||||
|
role:
|
||||||
|
(initialData.role as "company_admin" | "branch_admin" | "user") ||
|
||||||
|
"user",
|
||||||
|
roleId: initialData.Roles?.[0]?.id || "",
|
||||||
|
isActive: initialData.isActive || false,
|
||||||
|
branchId: initialData.branchId || "",
|
||||||
|
departmentId: initialData.departmentId || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [initialData, form]);
|
||||||
|
|
||||||
|
// Fetch available roles
|
||||||
|
const userRoles = [
|
||||||
|
{ value: "company_admin", label: "기업 관리자" },
|
||||||
|
{ value: "branch_admin", label: "지점 관리자" },
|
||||||
|
{ value: "user", label: "일반 유저" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Fetch branches
|
||||||
|
const { data: branches } = useQuery({
|
||||||
|
queryKey: ["branches"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await api.get<{ id: string; name: string }[]>(
|
||||||
|
"/api/v1/admin/branches"
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: !!token,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch departments
|
||||||
|
const { data: departments } = useQuery({
|
||||||
|
queryKey: ["departments", user?.companyId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await api.get<{ id: string; name: string }[]>(
|
||||||
|
`/api/v1/admin/departments/${user?.companyId}`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: !!token && !!user?.companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch roles
|
||||||
|
const { data: roles } = useQuery<Role[]>({
|
||||||
|
queryKey: ["roles", user?.companyId],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await api.get<Role[]>(
|
||||||
|
`/api/v1/admin/roles/${user?.companyId}`
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: !!token && !!user?.companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async (data: FormSchema) => {
|
||||||
|
try {
|
||||||
|
await onSubmit(data);
|
||||||
|
} catch (error) {
|
||||||
|
const err = error as AxiosError;
|
||||||
|
toast({
|
||||||
|
title: "에러",
|
||||||
|
description:
|
||||||
|
(err.response?.data as { message: string })?.message ||
|
||||||
|
"에러가 발생했습니다.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>아이디</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!initialData && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>비밀번호</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="password" {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>이름</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>이메일</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="email" {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="phone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>전화번호</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="role"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>역할</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
value={field.value || "user"}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="역할을 선택하세요" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{userRoles.map((role) => (
|
||||||
|
<SelectItem key={role.value} value={role.value}>
|
||||||
|
{role.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="roleId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>권한 그룹</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="권한 그룹을 선택하세요" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{roles?.map((role) => (
|
||||||
|
<SelectItem key={role.id} value={role.id}>
|
||||||
|
{role.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="isActive"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex items-center space-x-2">
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormLabel>활성화 여부</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="branchId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>지점</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="지점을 선택하세요" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{branches?.map((branch) => (
|
||||||
|
<SelectItem key={branch.id} value={branch.id}>
|
||||||
|
{branch.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="departmentId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>부서</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="부서를 선택하세요" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{departments?.map((dept) => (
|
||||||
|
<SelectItem key={dept.id} value={dept.id}>
|
||||||
|
{dept.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-2">
|
||||||
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
|
취소
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">저장</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
332
plm-app/src/app/(admin)/common/oemmng/page.tsx
Normal file
332
plm-app/src/app/(admin)/common/oemmng/page.tsx
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// src/app/(admin)/users/accounts/page.tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState, useCallback } from "react";
|
||||||
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { DataTable } from "@/components/ui/data-table";
|
||||||
|
import { api } from "@/lib/api";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { Plus, Edit, Trash2 } from "lucide-react";
|
||||||
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
import { UserForm } from "./components/UserForm";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { User, PaginatedResponse } from "@/types/user";
|
||||||
|
|
||||||
|
const AccountsPage = () => {
|
||||||
|
const { token, user } = useAuthStore();
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const [editingUser, setEditingUser] = React.useState<User | null>(null);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const { toast } = useToast();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
// Fetch users with pagination
|
||||||
|
const { data, isLoading } = useQuery<PaginatedResponse>({
|
||||||
|
queryKey: ["users", page, pageSize],
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await api.get("/api/v1/admin/users", {
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
limit: pageSize,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
users: data.users.map((user: User) => ({
|
||||||
|
...user,
|
||||||
|
Roles: user.Roles || [],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
enabled: !!token,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePageSizeChange = useCallback((newPageSize: number) => {
|
||||||
|
setPageSize(newPageSize);
|
||||||
|
setPage(1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Create user mutation
|
||||||
|
const createMutation = useMutation({
|
||||||
|
mutationFn: async (newUser: Partial<User>) => {
|
||||||
|
// Include companyId in the user data
|
||||||
|
const userWithCompanyId = {
|
||||||
|
...newUser,
|
||||||
|
companyId: user?.companyId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await api.post<User>(
|
||||||
|
"/api/v1/admin/users",
|
||||||
|
userWithCompanyId
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
setIsOpen(false);
|
||||||
|
toast({
|
||||||
|
title: "고객사 생성",
|
||||||
|
description: "새로운 고객사가 생성되었습니다.",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: AxiosError) => {
|
||||||
|
toast({
|
||||||
|
title: "고객사 생성 실패",
|
||||||
|
description: (error.response?.data as { message: string }).message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update user mutation
|
||||||
|
const updateMutation = useMutation({
|
||||||
|
mutationFn: async (userData: Partial<User>) => {
|
||||||
|
const { data } = await api.put<User>(
|
||||||
|
`/api/v1/admin/users/${userData.id}`,
|
||||||
|
userData
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
setIsOpen(false);
|
||||||
|
setEditingUser(null);
|
||||||
|
toast({
|
||||||
|
title: "고객사 수정",
|
||||||
|
description: "고객사 정보가 수정되었습니다.",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: AxiosError) => {
|
||||||
|
toast({
|
||||||
|
title: "고객사 수정 실패",
|
||||||
|
description: (error.response?.data as { message: string }).message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete user mutation
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: async (id: string) => {
|
||||||
|
await api.delete(`/api/v1/admin/users/${id}`);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["users"] });
|
||||||
|
toast({
|
||||||
|
title: "고객사 삭제",
|
||||||
|
description: "고객사가 삭제되었습니다.",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error: AxiosError) => {
|
||||||
|
toast({
|
||||||
|
title: "고객사 삭제 실패",
|
||||||
|
description: (error.response?.data as { message: string }).message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Table columns
|
||||||
|
const columns: ColumnDef<User>[] = [
|
||||||
|
{
|
||||||
|
id: "index",
|
||||||
|
header: "No",
|
||||||
|
cell: ({ row }) => row.original.index,
|
||||||
|
size: 60,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// accessorKey: "username",
|
||||||
|
// header: "아이디",
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
accessorKey: "name",
|
||||||
|
header: "업체명/고객사사",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "Department.name",
|
||||||
|
header: "업체/고객사코드드",
|
||||||
|
cell: ({ row }) => row.original.Department?.name || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "email",
|
||||||
|
header: "이메일",
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// accessorKey: "phone",
|
||||||
|
// header: "전화번호",
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
accessorKey: "role",
|
||||||
|
header: "역할",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const role = row.original.role;
|
||||||
|
const roleMap: Record<string, string> = {
|
||||||
|
super_admin: "슈퍼 관리자",
|
||||||
|
company_admin: "기업 관리자",
|
||||||
|
branch_admin: "지점 관리자",
|
||||||
|
user: "일반 고객사",
|
||||||
|
};
|
||||||
|
return roleMap[role] || role;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "roles",
|
||||||
|
header: "권한 그룹",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const roles = row.original.Roles;
|
||||||
|
return (roles ?? []).map((role) => role.name).join(", ");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// accessorKey: "Company.name",
|
||||||
|
// header: "회사",
|
||||||
|
// cell: ({ row }) => row.original.Company?.name || "-",
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
accessorKey: "Branch.name",
|
||||||
|
header: "지점",
|
||||||
|
cell: ({ row }) => row.original.Branch?.name || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "isActive",
|
||||||
|
header: "활성화",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Switch
|
||||||
|
checked={row.original.isActive}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
updateMutation.mutate({ id: row.original.id, isActive: value });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
header: "액션",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingUser(row.original);
|
||||||
|
setIsOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm("정말 삭제하시겠습니까?")) {
|
||||||
|
deleteMutation.mutate(row.original.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isLoading) return <div>Loading...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<h1 className="text-3xl font-bold">고객사 관리</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
고객사를 관리하고 권한을 설정합니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={() => setIsOpen(true)}>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
고객사 추가
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Users Table */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>고객사 목록</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{data?.users && data.users.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={data.users}
|
||||||
|
pagination={{
|
||||||
|
pageIndex: page - 1,
|
||||||
|
pageSize,
|
||||||
|
pageCount: data.totalPages,
|
||||||
|
rowCount: data.total,
|
||||||
|
onPageChange: (newPage) => setPage(newPage + 1),
|
||||||
|
onPageSizeChange: handlePageSizeChange,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="text-sm text-muted-foreground mt-2">
|
||||||
|
총 {data.total}명의 고객사
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
|
등록된 고객사가 없습니다.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* User Create/Edit Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsOpen(open);
|
||||||
|
if (!open) setEditingUser(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{editingUser ? "고객사 수정" : "새 고객사"}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{editingUser
|
||||||
|
? "기존 고객사 정보를 수정합니다."
|
||||||
|
: "새로운 고객사를 생성합니다."}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<UserForm
|
||||||
|
initialData={editingUser || undefined}
|
||||||
|
onSubmit={(data) => {
|
||||||
|
if (editingUser) {
|
||||||
|
updateMutation.mutate({ id: editingUser.id, ...data });
|
||||||
|
} else {
|
||||||
|
createMutation.mutate(data);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setEditingUser(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountsPage;
|
16
plm-app/src/types/common/oemmng/oemmng.ts
Normal file
16
plm-app/src/types/common/oemmng/oemmng.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// src/types/menu.ts
|
||||||
|
|
||||||
|
export interface oemMng {
|
||||||
|
id: string;
|
||||||
|
oem_code?: string | null;
|
||||||
|
oem_name?: string | null;
|
||||||
|
writer?: string | null;
|
||||||
|
regdate?: Date | null;
|
||||||
|
status?: string | null;
|
||||||
|
oem_no?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface oemMngResponse {
|
||||||
|
success: boolean;
|
||||||
|
data: oemMng[];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user