거지같아아
This commit is contained in:
parent
68101a1811
commit
241c1f94e4
190
plm-api/src/services/oemmng.service.js
Normal file
190
plm-api/src/services/oemmng.service.js
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// src/services/oemmng.service.js
|
||||||
|
const {
|
||||||
|
User,
|
||||||
|
Company,
|
||||||
|
Branch,
|
||||||
|
Department,
|
||||||
|
UserRole,
|
||||||
|
Role,
|
||||||
|
} = require("../models");
|
||||||
|
const { Op } = require("sequelize");
|
||||||
|
const alertService = require("./alert.service");
|
||||||
|
|
||||||
|
class OemMngService {
|
||||||
|
async findAll(currentUser, page = 1, limit = 10) {
|
||||||
|
try {
|
||||||
|
// Initialize the where clause
|
||||||
|
let where = {
|
||||||
|
role: { [Op.ne]: "super_admin" }, // Exclude super_admin users
|
||||||
|
};
|
||||||
|
|
||||||
|
// company_admin은 자신의 회사 유저만 조회 가능
|
||||||
|
if (currentUser.role === "company_admin") {
|
||||||
|
where.companyId = currentUser.companyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
|
// 전체 개수 조회
|
||||||
|
const count = await User.count({ where });
|
||||||
|
|
||||||
|
const users = await User.findAll({
|
||||||
|
where,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Company,
|
||||||
|
attributes: ["id", "name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Branch,
|
||||||
|
attributes: ["id", "name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Department,
|
||||||
|
attributes: ["id", "name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Role,
|
||||||
|
through: { attributes: [] },
|
||||||
|
attributes: ["id", "name", "description"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
distinct: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 인덱스 추가
|
||||||
|
const usersWithIndex = users.map((user, index) => {
|
||||||
|
const userJson = user.toJSON();
|
||||||
|
userJson.index = offset + index + 1;
|
||||||
|
return userJson;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: count,
|
||||||
|
totalPages: Math.ceil(count / limit),
|
||||||
|
currentPage: page,
|
||||||
|
users: usersWithIndex,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in findAll:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id) {
|
||||||
|
return await User.findByPk(id, {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: Company,
|
||||||
|
attributes: ["id", "name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Branch,
|
||||||
|
attributes: ["id", "name"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: Department,
|
||||||
|
attributes: ["id", "name"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createUser(userData, currentUser) {
|
||||||
|
const { roleId, ...userFields } = userData;
|
||||||
|
|
||||||
|
const user = await User.create(userFields);
|
||||||
|
|
||||||
|
if (roleId) {
|
||||||
|
const role = await Role.findOne({
|
||||||
|
where: {
|
||||||
|
id: roleId,
|
||||||
|
companyId: user.companyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
throw new Error("Role not found or does not belong to the company");
|
||||||
|
}
|
||||||
|
|
||||||
|
await UserRole.create({
|
||||||
|
userId: user.id,
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await alertService.createAlert({
|
||||||
|
type: "info",
|
||||||
|
message: `유저 ${user.name}이(가) ${currentUser.name}에 의해 생성되었습니다.`,
|
||||||
|
companyId: user.companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateUser(id, updateData, currentUser) {
|
||||||
|
const { roleId, ...userFields } = updateData;
|
||||||
|
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
if (!user) throw new Error("User not found");
|
||||||
|
|
||||||
|
// company_admin은 특정 필드를 수정할 수 없음 (예: role을 super_admin으로 변경 불가)
|
||||||
|
if (currentUser.role === "company_admin") {
|
||||||
|
if (updateData.role && updateData.role === "super_admin") {
|
||||||
|
throw new Error("super_admin 역할을 부여할 수 없습니다");
|
||||||
|
}
|
||||||
|
updateData.companyId = currentUser.companyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await user.update(userFields);
|
||||||
|
|
||||||
|
if (roleId) {
|
||||||
|
const role = await Role.findOne({
|
||||||
|
where: {
|
||||||
|
id: roleId,
|
||||||
|
companyId: user.companyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
throw new Error("Role not found or does not belong to the company");
|
||||||
|
}
|
||||||
|
|
||||||
|
await UserRole.upsert({
|
||||||
|
userId: user.id,
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await alertService.createAlert({
|
||||||
|
type: "info",
|
||||||
|
message: `유저 ${user.name}이(가) ${currentUser.name}에 의해 수정되었습니다.`,
|
||||||
|
companyId: user.companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(id, currentUser) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
if (!user) throw new Error("User not found");
|
||||||
|
|
||||||
|
const userName = user.name;
|
||||||
|
const companyId = user.companyId;
|
||||||
|
|
||||||
|
await user.destroy();
|
||||||
|
|
||||||
|
await alertService.createAlert({
|
||||||
|
type: "info",
|
||||||
|
message: `유저 ${userName}이(가) ${currentUser.name}에 의해 삭제되었습니다.`,
|
||||||
|
companyId: companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new UserService();
|
166
plm-app/src/app/(admin)/common/oemmng/components/OemMngForm.tsx
Normal file
166
plm-app/src/app/(admin)/common/oemmng/components/OemMngForm.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
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 {
|
||||||
|
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 { oemMng, Role } from "@/types/common/oemmng/oemmng";
|
||||||
|
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";
|
||||||
|
|
||||||
|
import { createValidationSchema } from "@/lib/utils"; // createValidationSchema 함수 임포트
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
{ name: 'oem_code', required: true, type: 'string' },
|
||||||
|
{ name: 'oem_name', required: true, type: 'string' },
|
||||||
|
{ name: 'oem_no', required: true, type: 'string' },
|
||||||
|
{ name: 'isActive', required: false, type: 'boolean' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = createValidationSchema(fields);
|
||||||
|
|
||||||
|
type FormSchema = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
interface OemFormProps {
|
||||||
|
initialData?: Partial<oemMng>;
|
||||||
|
onSubmit: (data: Partial<User>) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OemMngForm: React.FC<OemFormProps> = ({
|
||||||
|
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: {
|
||||||
|
oem_code: initialData?.oem_code || "",
|
||||||
|
oem_name: initialData?.oem_name || "",
|
||||||
|
oem_no: initialData?.oem_no || "",
|
||||||
|
isActive: initialData?.isActive || false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset form when initialData changes
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (initialData) {
|
||||||
|
form.reset({
|
||||||
|
oem_code: initialData.oem_code || "",
|
||||||
|
oem_name: initialData.oem_name || "",
|
||||||
|
oem_no: initialData.oem_no || "",
|
||||||
|
isActive: initialData.isActive || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [initialData, form]);
|
||||||
|
|
||||||
|
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="oem_code"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>OEM_CODE</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="oem_name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>OEM_NAME</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="oem_no"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>고객사번호</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} value={field.value || ""} />
|
||||||
|
</FormControl>
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-end space-x-2">
|
||||||
|
<Button type="button" variant="outline" onClick={onCancel}>
|
||||||
|
취소
|
||||||
|
</Button>
|
||||||
|
<Button type="submit">저장</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
@ -1,369 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -19,9 +19,9 @@ import { Plus, Edit, Trash2 } from "lucide-react";
|
|||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import { UserForm } from "./components/UserForm";
|
import { OemMngForm } from "./components/OemMngForm";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { User, PaginatedResponse } from "@/types/user";
|
import { oemMng, PaginatedResponse } from "@/types/common/oemmng/oemmng";
|
||||||
|
|
||||||
const AccountsPage = () => {
|
const AccountsPage = () => {
|
||||||
const { token, user } = useAuthStore();
|
const { token, user } = useAuthStore();
|
||||||
@ -159,46 +159,6 @@ const AccountsPage = () => {
|
|||||||
header: "업체/고객사코드드",
|
header: "업체/고객사코드드",
|
||||||
cell: ({ row }) => row.original.Department?.name || "-",
|
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",
|
accessorKey: "isActive",
|
||||||
header: "활성화",
|
header: "활성화",
|
||||||
@ -309,7 +269,7 @@ const AccountsPage = () => {
|
|||||||
: "새로운 고객사를 생성합니다."}
|
: "새로운 고객사를 생성합니다."}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<UserForm
|
<OemMngForm
|
||||||
initialData={editingUser || undefined}
|
initialData={editingUser || undefined}
|
||||||
onSubmit={(data) => {
|
onSubmit={(data) => {
|
||||||
if (editingUser) {
|
if (editingUser) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
import { clsx, type ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
import { z, ZodTypeAny } from 'zod';
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
@ -8,3 +9,37 @@ export function cn(...inputs: ClassValue[]) {
|
|||||||
export function formatNumber(num: number): string {
|
export function formatNumber(num: number): string {
|
||||||
return new Intl.NumberFormat("ko-KR").format(num);
|
return new Intl.NumberFormat("ko-KR").format(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Field {
|
||||||
|
name: string;
|
||||||
|
required: boolean;
|
||||||
|
type: 'string' | 'number' | 'email' | 'phone' | 'boolean';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createValidationSchema = (fields: Field[]) => {
|
||||||
|
const schema: Record<string, ZodTypeAny> = {};
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
switch (field.type) {
|
||||||
|
case 'string':
|
||||||
|
schema[field.name] = z.string().min(field.required ? 1 : 0, `${field.name}은 필수입니다.`);
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
schema[field.name] = z.number().min(field.required ? 1 : 0, `${field.name}은 필수입니다.`);
|
||||||
|
break;
|
||||||
|
case 'email':
|
||||||
|
schema[field.name] = z.string().email(`${field.name}은 유효한 이메일 형식이어야 합니다.`);
|
||||||
|
break;
|
||||||
|
case 'phone':
|
||||||
|
schema[field.name] = z.string().regex(/^[0-9-]+$/, `${field.name}은 올바른 전화번호 형식이어야 합니다.`);
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
schema[field.name] = z.boolean();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported field type: ${field.type}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return z.object(schema);
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
// src/types/menu.ts
|
|
||||||
|
|
||||||
export interface oemMng {
|
export interface oemMng {
|
||||||
id: string;
|
id: string;
|
||||||
@ -13,4 +13,11 @@ export interface oemMng {
|
|||||||
export interface oemMngResponse {
|
export interface oemMngResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data: oemMng[];
|
data: oemMng[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedResponse {
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
currentPage: number;
|
||||||
|
oemMngs: oemMng[];
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user