feat: 조직도 소속원 저장 및 선택 삭제 기능 작업
This commit is contained in:
@@ -5,8 +5,10 @@ import type {
|
|||||||
UserOrganizationParams,
|
UserOrganizationParams,
|
||||||
UserOrganizationList,
|
UserOrganizationList,
|
||||||
UserOrganizationService,
|
UserOrganizationService,
|
||||||
|
UserOrganizationPayloadData,
|
||||||
SearchUserParams,
|
SearchUserParams,
|
||||||
SearchUserList,
|
SearchUserList,
|
||||||
|
User,
|
||||||
} from "../model/user-organization-interface";
|
} from "../model/user-organization-interface";
|
||||||
|
|
||||||
export class UserOrganizationApiService implements UserOrganizationService {
|
export class UserOrganizationApiService implements UserOrganizationService {
|
||||||
@@ -34,6 +36,16 @@ export class UserOrganizationApiService implements UserOrganizationService {
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async saveUserOrganization(
|
||||||
|
data: UserOrganizationPayloadData[],
|
||||||
|
): Promise<User[]> {
|
||||||
|
const response = await client.post<User[]>(
|
||||||
|
`${ENDPOINT.CMS_SERVICE.USER_ORGANIZATION}`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserOrganizationApi = new UserOrganizationApiService();
|
export const UserOrganizationApi = new UserOrganizationApiService();
|
||||||
|
|||||||
@@ -81,4 +81,5 @@ export interface UserOrganizationService {
|
|||||||
): Promise<UserOrganizationList>;
|
): Promise<UserOrganizationList>;
|
||||||
getUserOrganizationTreeList(): Promise<TreeUserOrganization[]>;
|
getUserOrganizationTreeList(): Promise<TreeUserOrganization[]>;
|
||||||
searchUser(params: SearchUserParams): Promise<SearchUserList>;
|
searchUser(params: SearchUserParams): Promise<SearchUserList>;
|
||||||
|
saveUserOrganization(data: UserOrganizationPayloadData[]): Promise<User[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ function AdditionalUserDialog({
|
|||||||
const selectedRows = table.getSelectedRowModel().rows;
|
const selectedRows = table.getSelectedRowModel().rows;
|
||||||
const userData = selectedRows.map((row) => row.original);
|
const userData = selectedRows.map((row) => row.original);
|
||||||
if (!userData) return;
|
if (!userData) return;
|
||||||
|
|
||||||
onAddUsers(userData);
|
onAddUsers(userData);
|
||||||
|
table.setRowSelection({});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import React, { useEffect, useState, useCallback, memo } from "react";
|
import React, { useEffect, useState, useCallback, memo } from "react";
|
||||||
import { useFieldArray, useForm, type UseFormReturn } from "react-hook-form";
|
import { useFieldArray, useForm, type UseFormReturn } from "react-hook-form";
|
||||||
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useQuery, keepPreviousData } from "@tanstack/react-query";
|
import {
|
||||||
|
useQuery,
|
||||||
|
useMutation,
|
||||||
|
useQueryClient,
|
||||||
|
keepPreviousData,
|
||||||
|
} from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
@@ -24,11 +30,13 @@ import {
|
|||||||
UserOrganizationApi,
|
UserOrganizationApi,
|
||||||
userOrganizationFormSchema,
|
userOrganizationFormSchema,
|
||||||
type UserOrganizationFormData,
|
type UserOrganizationFormData,
|
||||||
|
type UserOrganizationPayloadData,
|
||||||
type User,
|
type User,
|
||||||
type SearchUser,
|
type SearchUser,
|
||||||
} from "@/entities/user-organization";
|
} from "@/entities/user-organization";
|
||||||
import { CodeApi, type ICode } from "@/entities/code";
|
import { CodeApi, type ICode } from "@/entities/code";
|
||||||
import { ENDPOINT } from "@/shared/config";
|
import { ENDPOINT } from "@/shared/config";
|
||||||
|
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||||
import { AdditionalUserDialog } from "./additional-user-dialog";
|
import { AdditionalUserDialog } from "./additional-user-dialog";
|
||||||
|
|
||||||
type UserOrganizationSectionProps = {
|
type UserOrganizationSectionProps = {
|
||||||
@@ -36,6 +44,10 @@ type UserOrganizationSectionProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { confirm } = useConfirm();
|
||||||
|
const { setMessage: alert } = useAlertStore((state) => state);
|
||||||
|
|
||||||
const [userList, setUserList] = useState<User[]>([]);
|
const [userList, setUserList] = useState<User[]>([]);
|
||||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
||||||
|
|
||||||
@@ -59,17 +71,29 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
queryFn: () => CodeApi.getCode("JDGDCLCD"),
|
queryFn: () => CodeApi.getCode("JDGDCLCD"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { mutate: saveUserOrganization } = useMutation({
|
||||||
|
mutationFn: (data: UserOrganizationPayloadData[]) =>
|
||||||
|
UserOrganizationApi.saveUserOrganization(data),
|
||||||
|
onSuccess: () => {
|
||||||
|
alert("저장되었습니다.");
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [ENDPOINT.CMS_SERVICE.USER_ORGANIZATION, ognzSn],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const form = useForm<UserOrganizationFormData>({
|
const form = useForm<UserOrganizationFormData>({
|
||||||
resolver: zodResolver(userOrganizationFormSchema),
|
resolver: zodResolver(userOrganizationFormSchema),
|
||||||
values: {
|
values: {
|
||||||
userOrganization: userList.map((user) => ({
|
userOrganization:
|
||||||
mode: user.mode,
|
userOrganizationList?.content.map((user) => ({
|
||||||
ognzSn: user.ognzSn,
|
mode: user.mode,
|
||||||
userUnqId: user.userUnqId,
|
ognzSn: user.ognzSn,
|
||||||
jbpsSeCd: user.jbpsSeCd,
|
userUnqId: user.userUnqId,
|
||||||
jbgdSeCd: user.jbgdSeCd,
|
jbpsSeCd: user.jbpsSeCd,
|
||||||
sortSeq: user.sortSeq,
|
jbgdSeCd: user.jbgdSeCd,
|
||||||
})),
|
sortSeq: user.sortSeq,
|
||||||
|
})) ?? [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,30 +103,25 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleAddUsers = (users: SearchUser[]) => {
|
const handleAddUsers = (users: SearchUser[]) => {
|
||||||
const newForm = users.map((user) => ({
|
const generateUserData = (user: SearchUser) => ({
|
||||||
mode: "C",
|
mode: "C",
|
||||||
ognzSn,
|
ognzSn,
|
||||||
userUnqId: user.userUnqId,
|
userUnqId: user.userUnqId,
|
||||||
jbpsSeCd: "",
|
jbpsSeCd: "",
|
||||||
jbgdSeCd: "",
|
jbgdSeCd: "",
|
||||||
sortSeq: 0,
|
sortSeq: 0,
|
||||||
}));
|
});
|
||||||
append(newForm);
|
|
||||||
|
const newFormData = users.map(generateUserData);
|
||||||
|
append(newFormData);
|
||||||
|
|
||||||
const newUserList = users.map((user) => ({
|
const newUserList = users.map((user) => ({
|
||||||
mode: "C",
|
...generateUserData(user),
|
||||||
ognzSn,
|
|
||||||
userUnqId: user.userUnqId,
|
|
||||||
userUnqNm: user.userNm,
|
userUnqNm: user.userNm,
|
||||||
jbpsSeCd: "",
|
|
||||||
jbgdSeCd: "",
|
|
||||||
sortSeq: 0,
|
|
||||||
}));
|
}));
|
||||||
|
const existingUserIds = new Set(newUserList.map((user) => user.userUnqId));
|
||||||
const filteredUserList = userList.filter(
|
const filteredUserList = userList.filter(
|
||||||
(existingUser) =>
|
(user) => !existingUserIds.has(user.userUnqId),
|
||||||
!newUserList.some(
|
|
||||||
(newUser) => newUser.userUnqId === existingUser.userUnqId,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
setUserList([...newUserList, ...filteredUserList]);
|
setUserList([...newUserList, ...filteredUserList]);
|
||||||
};
|
};
|
||||||
@@ -130,16 +149,29 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteUsers = () => {
|
const handleDeleteUsers = () => {
|
||||||
if (selectedUsers.length === 0) return;
|
|
||||||
|
|
||||||
const selectedUserIds = selectedUsers.map((user) => user.userUnqId);
|
const selectedUserIds = selectedUsers.map((user) => user.userUnqId);
|
||||||
|
const updatedFormData = form.getValues("userOrganization").map((item) => {
|
||||||
|
if (selectedUserIds.includes(item.userUnqId)) {
|
||||||
|
return { ...item, mode: "D" };
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
form.setValue("userOrganization", updatedFormData);
|
||||||
setUserList((prevUsers) =>
|
setUserList((prevUsers) =>
|
||||||
prevUsers.filter((user) => !selectedUserIds.includes(user.userUnqId)),
|
prevUsers.filter((user) => !selectedUserIds.includes(user.userUnqId)),
|
||||||
);
|
);
|
||||||
setSelectedUsers([]);
|
setSelectedUsers([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveUsers = async () => {
|
||||||
|
if (await confirm("저장하시겠습니까?")) {
|
||||||
|
const filteredData = form
|
||||||
|
.watch("userOrganization")
|
||||||
|
.filter((item) => item.mode !== "R");
|
||||||
|
saveUserOrganization(filteredData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const isUserSelected = useCallback(
|
const isUserSelected = useCallback(
|
||||||
(userUnqId: string) =>
|
(userUnqId: string) =>
|
||||||
selectedUsers.some((item) => item.userUnqId === userUnqId),
|
selectedUsers.some((item) => item.userUnqId === userUnqId),
|
||||||
@@ -166,7 +198,9 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
>
|
>
|
||||||
선택삭제
|
선택삭제
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm">저장</Button>
|
<Button size="sm" onClick={handleSaveUsers}>
|
||||||
|
저장
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
@@ -196,12 +230,12 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
onCheckedChange={() => handleToggleSelectUser(user)}
|
onCheckedChange={() => handleToggleSelectUser(user)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<UserTableRow
|
<UserCells
|
||||||
form={form}
|
form={form}
|
||||||
user={user}
|
user={user}
|
||||||
index={index}
|
index={index}
|
||||||
jobPositionCodes={jobPositionCodes}
|
jobPositionCodeData={jobPositionCodes}
|
||||||
jobGradeCodes={jobGradeCodes}
|
jobGradeCodeData={jobGradeCodes}
|
||||||
/>
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
@@ -212,28 +246,28 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserTableRowProps = {
|
type UserCellsProps = {
|
||||||
form: UseFormReturn<UserOrganizationFormData>;
|
form: UseFormReturn<UserOrganizationFormData>;
|
||||||
user: User;
|
user: User;
|
||||||
index: number;
|
index: number;
|
||||||
jobPositionCodes?: ICode[];
|
jobPositionCodeData?: ICode[];
|
||||||
jobGradeCodes?: ICode[];
|
jobGradeCodeData?: ICode[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserTableRow = memo(
|
const UserCells = memo(
|
||||||
({
|
({
|
||||||
form,
|
form,
|
||||||
user,
|
user,
|
||||||
index,
|
index,
|
||||||
jobPositionCodes,
|
jobPositionCodeData,
|
||||||
jobGradeCodes,
|
jobGradeCodeData,
|
||||||
}: UserTableRowProps) => (
|
}: UserCellsProps) => (
|
||||||
<>
|
<>
|
||||||
<TableCell align="center">{index + 1}</TableCell>
|
<TableCell align="center">{index + 1}</TableCell>
|
||||||
<TableCell align="center">{user.userUnqNm}</TableCell>
|
<TableCell align="center">{user.userUnqNm}</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<CodeSelect
|
<CodeSelect
|
||||||
codeData={jobPositionCodes}
|
codeData={jobPositionCodeData}
|
||||||
defaultValue={user.jbpsSeCd}
|
defaultValue={user.jbpsSeCd}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const mode = form.watch(`userOrganization.${index}.mode`);
|
const mode = form.watch(`userOrganization.${index}.mode`);
|
||||||
@@ -247,7 +281,7 @@ const UserTableRow = memo(
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<CodeSelect
|
<CodeSelect
|
||||||
codeData={jobGradeCodes}
|
codeData={jobGradeCodeData}
|
||||||
defaultValue={user.jbgdSeCd}
|
defaultValue={user.jbgdSeCd}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
const mode = form.watch(`userOrganization.${index}.mode`);
|
const mode = form.watch(`userOrganization.${index}.mode`);
|
||||||
@@ -267,7 +301,7 @@ const UserTableRow = memo(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
UserTableRow.displayName = "UserTableRow";
|
UserCells.displayName = "UserCells";
|
||||||
|
|
||||||
type CodeSelectProps = React.ComponentPropsWithoutRef<typeof Select> & {
|
type CodeSelectProps = React.ComponentPropsWithoutRef<typeof Select> & {
|
||||||
codeData?: ICode[];
|
codeData?: ICode[];
|
||||||
@@ -276,14 +310,14 @@ type CodeSelectProps = React.ComponentPropsWithoutRef<typeof Select> & {
|
|||||||
const CodeSelect = memo(({ codeData, ...props }: CodeSelectProps) => {
|
const CodeSelect = memo(({ codeData, ...props }: CodeSelectProps) => {
|
||||||
if (!codeData) return null;
|
if (!codeData) return null;
|
||||||
|
|
||||||
const defaultValueText =
|
const placeholderText =
|
||||||
codeData.find((item) => item.lwpoCd === props.defaultValue)?.lwpoCdNm ??
|
codeData.find((item) => item.lwpoCd === props.defaultValue)?.lwpoCdNm ??
|
||||||
"선택";
|
"선택";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select {...props}>
|
<Select {...props}>
|
||||||
<SelectTrigger className="w-[12rem]">
|
<SelectTrigger className="w-[10rem]">
|
||||||
<SelectValue placeholder={defaultValueText} />
|
<SelectValue placeholder={placeholderText} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{codeData.map((item) => (
|
{codeData.map((item) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user