feat: 조직도 폼 작업
This commit is contained in:
@@ -15,13 +15,15 @@ export class OrganizationApiService implements OrganizationService {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createOrganization(
|
||||
organization: OrganizationFormData,
|
||||
): Promise<void> {
|
||||
await client.post<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.ORGANIZATIONS}`,
|
||||
organization,
|
||||
public async getOrganizationInfo(ognzSn: number): Promise<Organization> {
|
||||
const response = await client.get<Organization>(
|
||||
`${ENDPOINT.CMS_SERVICE.ORGANIZATIONS}/${ognzSn}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async createOrganization(form: OrganizationFormData): Promise<void> {
|
||||
await client.post<void>(`${ENDPOINT.CMS_SERVICE.ORGANIZATIONS}`, form);
|
||||
}
|
||||
|
||||
public async deleteOrganization(ognzSn: number): Promise<void> {
|
||||
@@ -39,6 +41,16 @@ export class OrganizationApiService implements OrganizationService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async updateOrganization(
|
||||
ognzSn: number,
|
||||
form: OrganizationFormData,
|
||||
): Promise<void> {
|
||||
await client.put<void>(
|
||||
`${ENDPOINT.CMS_SERVICE.ORGANIZATIONS}/${ognzSn}`,
|
||||
form,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const OrganizationApi = new OrganizationApiService();
|
||||
|
||||
@@ -21,7 +21,9 @@ export type OrganizationTree = Organization & {
|
||||
|
||||
export interface OrganizationService {
|
||||
getOrganizationsTreeList(): Promise<OrganizationTree[]>;
|
||||
createOrganization(organization: OrganizationFormData): Promise<void>;
|
||||
getOrganizationInfo(ognzSn: number): Promise<Organization>;
|
||||
createOrganization(form: OrganizationFormData): Promise<void>;
|
||||
deleteOrganization(ognzSn: number): Promise<void>;
|
||||
updateOrganizationName(ognzSn: number, name: string): Promise<Organization>;
|
||||
updateOrganization(ognzSn: number, form: OrganizationFormData): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
"use client";
|
||||
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import type { OrganizationFormData } from "@/entities/organization";
|
||||
import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form";
|
||||
|
||||
type OrganizationFormProps = {
|
||||
form: UseFormReturn<OrganizationFormData>;
|
||||
ognzSn?: number;
|
||||
handleCreate: (data: OrganizationFormData) => Promise<void>;
|
||||
handleUpdate: ({
|
||||
ognzSn,
|
||||
data,
|
||||
}: {
|
||||
ognzSn: number;
|
||||
data: OrganizationFormData;
|
||||
}) => Promise<void>;
|
||||
handleDelete?: (ognzSn: number) => Promise<void>;
|
||||
};
|
||||
|
||||
function OrganizationForm({
|
||||
form,
|
||||
ognzSn,
|
||||
handleCreate,
|
||||
handleUpdate,
|
||||
handleDelete,
|
||||
}: OrganizationFormProps) {
|
||||
const isModifyMode = !!ognzSn;
|
||||
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const onSubmit = async (data: OrganizationFormData) => {
|
||||
if (isModifyMode) {
|
||||
await onUpdate(data);
|
||||
} else {
|
||||
await onCreate(data);
|
||||
}
|
||||
};
|
||||
|
||||
const onCreate = async (data: OrganizationFormData) => {
|
||||
if (await confirm("저장하시겠습니까?")) {
|
||||
handleCreate(data);
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdate = async (data: OrganizationFormData) => {
|
||||
if (await confirm("수정하시겠습니까?")) {
|
||||
handleUpdate({ ognzSn: ognzSn!, data });
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
if (await confirm("삭제하시겠습니까?")) {
|
||||
handleDelete?.(ognzSn!);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="card">
|
||||
<Table variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="85%" />
|
||||
</colgroup>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>조직명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="ognzNm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="조직명을 입력해주세요"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>조직설명</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="ognzExpln"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="조직설명을 입력해주세요"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="sortSeq"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
value={field.value ?? 0}
|
||||
onChange={(e) =>
|
||||
field.onChange(Number(e.target.value))
|
||||
}
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="정렬순서를 입력해주세요"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사용여부</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="useYn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
{...field}
|
||||
name="useYn"
|
||||
value={field.value ?? ""}
|
||||
onValueChange={field.onChange}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="Y" id="useY" />
|
||||
<Label htmlFor="useY">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="N" id="useN" />
|
||||
<Label htmlFor="useN">사용안함</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex justify-center gap-4 pb-12 pt-10">
|
||||
<Button size="lg" type="submit">
|
||||
저장
|
||||
</Button>
|
||||
<Button
|
||||
size="lg"
|
||||
color="red"
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
disabled={!isModifyMode}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export { OrganizationForm };
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@/entities/organization";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore } from "@/shared/lib/use-alert-store";
|
||||
import { OrganizationForm } from "./organization-form";
|
||||
|
||||
function OrganizationPage() {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -34,9 +35,19 @@ function OrganizationPage() {
|
||||
select: (data) => generateOrganizationTreeData(data),
|
||||
});
|
||||
|
||||
const { data: organizationInfo } = useQuery({
|
||||
queryKey: [
|
||||
ENDPOINT.CMS_SERVICE.ORGANIZATIONS,
|
||||
selectedOrganization?.ognzSn,
|
||||
],
|
||||
queryFn: () =>
|
||||
OrganizationApi.getOrganizationInfo(selectedOrganization!.ognzSn!),
|
||||
enabled: !!selectedOrganization?.ognzSn,
|
||||
});
|
||||
|
||||
const { mutateAsync: createOrganization } = useMutation({
|
||||
mutationFn: (organization: OrganizationFormData) =>
|
||||
OrganizationApi.createOrganization(organization),
|
||||
mutationFn: (form: OrganizationFormData) =>
|
||||
OrganizationApi.createOrganization(form),
|
||||
onSuccess: () => {
|
||||
alert("저장되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
@@ -49,6 +60,7 @@ function OrganizationPage() {
|
||||
mutationFn: (ognzSn: number) => OrganizationApi.deleteOrganization(ognzSn),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.");
|
||||
setSelectedOrganization(null);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.ORGANIZATIONS],
|
||||
});
|
||||
@@ -66,6 +78,32 @@ function OrganizationPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateOrganization } = useMutation({
|
||||
mutationFn: ({
|
||||
ognzSn,
|
||||
data,
|
||||
}: {
|
||||
ognzSn: number;
|
||||
data: OrganizationFormData;
|
||||
}) => OrganizationApi.updateOrganization(ognzSn, data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.ORGANIZATIONS],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const form = useForm<OrganizationFormData>({
|
||||
resolver: zodResolver(organizationFormSchema),
|
||||
defaultValues: {
|
||||
ognzNm: "",
|
||||
sortSeq: 1,
|
||||
useYn: "Y",
|
||||
},
|
||||
values: organizationInfo,
|
||||
});
|
||||
|
||||
const additionalNodeForm = useForm<OrganizationFormData>({
|
||||
resolver: zodResolver(organizationFormSchema),
|
||||
defaultValues: {
|
||||
@@ -159,10 +197,19 @@ function OrganizationPage() {
|
||||
updateNodeName={async (id, name) => {
|
||||
await updateOrganizationName({ ognzSn: id as number, name });
|
||||
}}
|
||||
classes={{ wrapper: "max-h-[calc(100vh-29rem)]" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card card-border flex-1"></div>
|
||||
<div className="card card-border flex-1">
|
||||
<OrganizationForm
|
||||
form={form}
|
||||
ognzSn={selectedOrganization?.ognzSn ?? 0}
|
||||
handleCreate={createOrganization}
|
||||
handleUpdate={updateOrganization}
|
||||
handleDelete={deleteOrganization}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type DropOptions,
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { cn } from "@common/components/lib";
|
||||
import { CustomNode } from "./custom-node";
|
||||
|
||||
interface TreeData extends Record<string, unknown> {
|
||||
@@ -19,9 +20,12 @@ interface TreeData extends Record<string, unknown> {
|
||||
|
||||
type EditableNodeProps = { depth: number };
|
||||
|
||||
type TreeProps<T extends TreeData> = {
|
||||
type DndTreeProps<T extends TreeData> = {
|
||||
tree: NodeModel<T>[];
|
||||
selectedNodeId: number;
|
||||
classes?: {
|
||||
wrapper: string;
|
||||
};
|
||||
renderEditableNode: (props: EditableNodeProps) => ReactElement;
|
||||
handleSelectNode: (node: NodeModel<T>) => void;
|
||||
handleDropNode: (newTree: NodeModel<T>[], options: DropOptions<T>) => void;
|
||||
@@ -31,11 +35,12 @@ type TreeProps<T extends TreeData> = {
|
||||
function DndTree<T extends TreeData>({
|
||||
tree,
|
||||
selectedNodeId,
|
||||
classes,
|
||||
renderEditableNode,
|
||||
handleSelectNode,
|
||||
handleDropNode,
|
||||
updateNodeName,
|
||||
}: TreeProps<T>) {
|
||||
}: DndTreeProps<T>) {
|
||||
const treeMethods = useRef<TreeMethods>(null);
|
||||
|
||||
const isAdditionalParent = (node: NodeModel<T>): boolean => {
|
||||
@@ -46,7 +51,12 @@ function DndTree<T extends TreeData>({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="max-h-[calc(100vh-33.5rem)] overflow-auto">
|
||||
<div
|
||||
className={cn(
|
||||
"max-h-[calc(100vh-33.5rem)] overflow-auto",
|
||||
classes?.wrapper,
|
||||
)}
|
||||
>
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={tree}
|
||||
|
||||
Reference in New Issue
Block a user