refactor: 조직도 소속원 폼 리팩토링
This commit is contained in:
@@ -16,18 +16,18 @@ export interface SearchUserParams extends PageableParams {
|
||||
}
|
||||
|
||||
export interface User {
|
||||
mode: string;
|
||||
ognzSn: number;
|
||||
userUnqId: string;
|
||||
userUnqNm: string;
|
||||
userUnqNm?: string;
|
||||
jbpsSeCd: string;
|
||||
jbpsSeNm: string;
|
||||
jbpsSeNm?: string;
|
||||
jbgdSeCd: string;
|
||||
jbgdSeNm: string;
|
||||
jbgdSeNm?: string;
|
||||
sortSeq: number;
|
||||
mdfcnDt: string;
|
||||
mdfr: string;
|
||||
mdfrNm: string;
|
||||
mode: string;
|
||||
mdfcnDt?: string;
|
||||
mdfr?: string;
|
||||
mdfrNm?: string;
|
||||
}
|
||||
|
||||
export interface SearchUser {
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import dayjs from "dayjs";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Checkbox, Select } from "@common/components/ui";
|
||||
import type {
|
||||
User,
|
||||
UserOrganizationFormData,
|
||||
} from "@/entities/user-organization";
|
||||
|
||||
export const createColumns = (
|
||||
form: UseFormReturn<UserOrganizationFormData>,
|
||||
renderJobPositionSelect: (
|
||||
props: React.ComponentPropsWithoutRef<typeof Select>,
|
||||
) => React.ReactNode,
|
||||
renderJobGradeSelect: (
|
||||
props: React.ComponentPropsWithoutRef<typeof Select>,
|
||||
) => React.ReactNode,
|
||||
): ColumnDef<User>[] => {
|
||||
return [
|
||||
{
|
||||
id: "select",
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={table.getIsAllPageRowsSelected()}
|
||||
onCheckedChange={(checked) =>
|
||||
table.toggleAllPageRowsSelected(!!checked)
|
||||
}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(checked) => row.toggleSelected(!!checked)}
|
||||
className="h-4 w-4 rounded border-gray-300"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "no",
|
||||
header: "NO",
|
||||
cell: ({ row }) => row.index + 1,
|
||||
},
|
||||
{
|
||||
accessorKey: "userUnqNm",
|
||||
header: "이름",
|
||||
},
|
||||
{
|
||||
accessorKey: "jbpsSeCd",
|
||||
header: "직위",
|
||||
cell: ({ row }) =>
|
||||
renderJobPositionSelect({
|
||||
defaultValue: row.original.jbpsSeCd,
|
||||
value: form.watch(`userOrganization.${row.index}.jbpsSeCd`),
|
||||
onValueChange: (value) => {
|
||||
const mode = form.watch(`userOrganization.${row.index}.mode`);
|
||||
if (mode === "R")
|
||||
form.setValue(`userOrganization.${row.index}.mode`, "U");
|
||||
form.setValue(`userOrganization.${row.index}.jbpsSeCd`, value);
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
accessorKey: "jbgdSeCd",
|
||||
header: "직급",
|
||||
cell: ({ row }) =>
|
||||
renderJobGradeSelect({
|
||||
defaultValue: row.original.jbgdSeCd,
|
||||
value: form.watch(`userOrganization.${row.index}.jbgdSeCd`),
|
||||
onValueChange: (value) => {
|
||||
const mode = form.watch(`userOrganization.${row.index}.mode`);
|
||||
if (mode === "R")
|
||||
form.setValue(`userOrganization.${row.index}.mode`, "U");
|
||||
form.setValue(`userOrganization.${row.index}.jbgdSeCd`, value);
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
accessorKey: "mdfrNm",
|
||||
header: "최종수정자",
|
||||
},
|
||||
{
|
||||
accessorKey: "mdfcnDt",
|
||||
header: "최종수정일",
|
||||
cell: ({ row }) => {
|
||||
if (!row.original.mdfcnDt) return "";
|
||||
return dayjs(row.original.mdfcnDt).format("YYYY-MM-DD");
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -1,10 +1,18 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import dayjs from "dayjs";
|
||||
import React, { useEffect, useState, useCallback, memo } from "react";
|
||||
import { useFieldArray, useForm, type UseFormReturn } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery, keepPreviousData } from "@tanstack/react-query";
|
||||
import { useReactTable, getCoreRowModel } from "@tanstack/react-table";
|
||||
import { cn } from "@common/components/lib";
|
||||
import { Button, DataTable } from "@common/components/ui";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@common/components/ui";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -21,20 +29,22 @@ import {
|
||||
} from "@/entities/user-organization";
|
||||
import { CodeApi, type ICode } from "@/entities/code";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { createColumns } from "../model/user-organization-table-columns";
|
||||
import { AdditionalUserDialog } from "./additional-user-dialog";
|
||||
|
||||
type UserOrganizationSectionProps = {
|
||||
ognzSn?: number | null;
|
||||
ognzSn: number;
|
||||
};
|
||||
|
||||
function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
||||
const [userList, setUserList] = useState<User[]>([]);
|
||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
||||
|
||||
const isAllSelected =
|
||||
userList.length > 0 && selectedUsers.length === userList.length;
|
||||
|
||||
const { data: userOrganizationList } = useQuery({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.USER_ORGANIZATION, ognzSn],
|
||||
queryFn: () =>
|
||||
UserOrganizationApi.getUserOrganizationList({ ognzSn: ognzSn! }),
|
||||
queryFn: () => UserOrganizationApi.getUserOrganizationList({ ognzSn }),
|
||||
enabled: !!ognzSn,
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
@@ -52,37 +62,26 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
||||
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: userList.map((user) => ({
|
||||
mode: user.mode,
|
||||
ognzSn: user.ognzSn,
|
||||
userUnqId: user.userUnqId,
|
||||
jbpsSeCd: user.jbpsSeCd,
|
||||
jbgdSeCd: user.jbgdSeCd,
|
||||
sortSeq: user.sortSeq,
|
||||
})),
|
||||
},
|
||||
});
|
||||
const { append } = useFieldArray({
|
||||
control: form.control,
|
||||
name: "userOrganization",
|
||||
});
|
||||
|
||||
const table = useReactTable({
|
||||
data: userList,
|
||||
columns: createColumns(
|
||||
form,
|
||||
(props) => <CodeSelect codeData={jobPositionCodes} {...props} />,
|
||||
(props) => <CodeSelect codeData={jobGradeCodes} {...props} />,
|
||||
),
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
const { append } = useFieldArray({
|
||||
name: "userOrganization",
|
||||
control: form.control,
|
||||
});
|
||||
|
||||
const handleAddUsers = (users: SearchUser[]) => {
|
||||
const newForm = users.map((user) => ({
|
||||
mode: "C",
|
||||
ognzSn: ognzSn!,
|
||||
ognzSn,
|
||||
userUnqId: user.userUnqId,
|
||||
jbpsSeCd: "",
|
||||
jbgdSeCd: "",
|
||||
@@ -91,51 +90,184 @@ function UserOrganizationSection({ ognzSn }: UserOrganizationSectionProps) {
|
||||
append(newForm);
|
||||
|
||||
const newUserList = users.map((user) => ({
|
||||
ognzSn: ognzSn!,
|
||||
mode: "C",
|
||||
ognzSn,
|
||||
userUnqId: user.userUnqId,
|
||||
userUnqNm: user.userNm,
|
||||
jbpsSeCd: "",
|
||||
jbpsSeNm: "",
|
||||
jbgdSeCd: "",
|
||||
jbgdSeNm: "",
|
||||
sortSeq: 0,
|
||||
mdfcnDt: "",
|
||||
mdfr: "",
|
||||
mdfrNm: "",
|
||||
mode: "C",
|
||||
}));
|
||||
setUserList([...newUserList, ...userList]);
|
||||
};
|
||||
|
||||
const handleToggleSelectUser = useCallback((user: User) => {
|
||||
setSelectedUsers((prevSelected) => {
|
||||
const isSelected = prevSelected.some(
|
||||
(item) => item.userUnqId === user.userUnqId,
|
||||
);
|
||||
|
||||
if (isSelected) {
|
||||
return prevSelected.filter((item) => item.userUnqId !== user.userUnqId);
|
||||
} else {
|
||||
return [...prevSelected, user];
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleToggleSelectAll = () => {
|
||||
if (isAllSelected) {
|
||||
setSelectedUsers([]);
|
||||
} else {
|
||||
setSelectedUsers([...userList]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUsers = () => {
|
||||
if (selectedUsers.length === 0) return;
|
||||
|
||||
const selectedUserIds = selectedUsers.map((user) => user.userUnqId);
|
||||
|
||||
setUserList((prevUsers) =>
|
||||
prevUsers.filter((user) => !selectedUserIds.includes(user.userUnqId)),
|
||||
);
|
||||
setSelectedUsers([]);
|
||||
};
|
||||
|
||||
const isUserSelected = useCallback(
|
||||
(userUnqId: string) =>
|
||||
selectedUsers.some((item) => item.userUnqId === userUnqId),
|
||||
[selectedUsers],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (userOrganizationList?.content)
|
||||
if (userOrganizationList?.content) {
|
||||
setUserList(userOrganizationList.content);
|
||||
}
|
||||
}, [userOrganizationList]);
|
||||
|
||||
return (
|
||||
<div className={cn("card card-border", !ognzSn && "hidden")}>
|
||||
<div className="card card-border">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">소속원 목록</h3>
|
||||
<div className="flex gap-2">
|
||||
<AdditionalUserDialog onAddUsers={handleAddUsers} />
|
||||
<Button size="sm" color="red">
|
||||
<Button
|
||||
size="sm"
|
||||
color="red"
|
||||
onClick={handleDeleteUsers}
|
||||
disabled={selectedUsers.length === 0}
|
||||
>
|
||||
선택삭제
|
||||
</Button>
|
||||
<Button size="sm">저장</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<DataTable table={table} />
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={handleToggleSelectAll}
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead>NO</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead>직위</TableHead>
|
||||
<TableHead>직급</TableHead>
|
||||
<TableHead>최종수정자</TableHead>
|
||||
<TableHead>최종수정일</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{userList.map((user, index) => (
|
||||
<TableRow key={user.userUnqId}>
|
||||
<TableCell align="center">
|
||||
<Checkbox
|
||||
checked={isUserSelected(user.userUnqId)}
|
||||
onCheckedChange={() => handleToggleSelectUser(user)}
|
||||
/>
|
||||
</TableCell>
|
||||
<UserTableRow
|
||||
form={form}
|
||||
user={user}
|
||||
index={index}
|
||||
jobPositionCodes={jobPositionCodes}
|
||||
jobGradeCodes={jobGradeCodes}
|
||||
/>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type UserTableRowProps = {
|
||||
form: UseFormReturn<UserOrganizationFormData>;
|
||||
user: User;
|
||||
index: number;
|
||||
jobPositionCodes?: ICode[];
|
||||
jobGradeCodes?: ICode[];
|
||||
};
|
||||
|
||||
const UserTableRow = memo(
|
||||
({
|
||||
form,
|
||||
user,
|
||||
index,
|
||||
jobPositionCodes,
|
||||
jobGradeCodes,
|
||||
}: UserTableRowProps) => (
|
||||
<>
|
||||
<TableCell align="center">{index + 1}</TableCell>
|
||||
<TableCell align="center">{user.userUnqNm}</TableCell>
|
||||
<TableCell align="center">
|
||||
<CodeSelect
|
||||
codeData={jobPositionCodes}
|
||||
defaultValue={user.jbpsSeCd}
|
||||
onValueChange={(value) => {
|
||||
const mode = form.watch(`userOrganization.${index}.mode`);
|
||||
if (mode === "R") {
|
||||
form.setValue(`userOrganization.${index}.mode`, "U");
|
||||
}
|
||||
form.setValue(`userOrganization.${index}.jbpsSeCd`, value);
|
||||
}}
|
||||
{...form.register(`userOrganization.${index}.jbpsSeCd`)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<CodeSelect
|
||||
codeData={jobGradeCodes}
|
||||
defaultValue={user.jbgdSeCd}
|
||||
onValueChange={(value) => {
|
||||
const mode = form.watch(`userOrganization.${index}.mode`);
|
||||
if (mode === "R") {
|
||||
form.setValue(`userOrganization.${index}.mode`, "U");
|
||||
}
|
||||
form.setValue(`userOrganization.${index}.jbgdSeCd`, value);
|
||||
}}
|
||||
{...form.register(`userOrganization.${index}.jbgdSeCd`)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">{user.mdfrNm}</TableCell>
|
||||
<TableCell align="center">
|
||||
{dayjs(user.mdfcnDt).format("YYYY-MM-DD")}
|
||||
</TableCell>
|
||||
</>
|
||||
),
|
||||
);
|
||||
|
||||
UserTableRow.displayName = "UserTableRow";
|
||||
|
||||
type CodeSelectProps = React.ComponentPropsWithoutRef<typeof Select> & {
|
||||
codeData?: ICode[];
|
||||
};
|
||||
|
||||
const CodeSelect = ({ codeData, ...props }: CodeSelectProps) => {
|
||||
const CodeSelect = memo(({ codeData, ...props }: CodeSelectProps) => {
|
||||
if (!codeData) return null;
|
||||
|
||||
const defaultValueText =
|
||||
@@ -156,6 +288,8 @@ const CodeSelect = ({ codeData, ...props }: CodeSelectProps) => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
CodeSelect.displayName = "CodeSelect";
|
||||
|
||||
export { UserOrganizationSection };
|
||||
|
||||
Reference in New Issue
Block a user