feat: 조직도 소속원 저장 및 선택 삭제 기능 작업

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