[as] feat: 검토인력관리 페이지 구현
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { OgnzUserTreeUserDto } from "@/entities/user-organization";
|
||||
import type { SelectedOgnzUser } from "@/widgets/ognz-user-tree";
|
||||
|
||||
export interface ReviewerOption {
|
||||
uuid: string;
|
||||
@@ -6,10 +6,10 @@ export interface ReviewerOption {
|
||||
deptNm: string;
|
||||
}
|
||||
|
||||
export function toReviewerOption(user: OgnzUserTreeUserDto, ognzNm: string): ReviewerOption {
|
||||
export function fromSelectedOgnzUser(user: SelectedOgnzUser): ReviewerOption {
|
||||
return {
|
||||
uuid: user.userUnqId,
|
||||
uuid: user.userUuid,
|
||||
name: user.userNm,
|
||||
deptNm: ognzNm,
|
||||
deptNm: user.ognzNm,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { useOgnzUserTree } from "@/entities/user-organization";
|
||||
import {
|
||||
collectExpandedOgnzSnIds,
|
||||
filterOgnzUserTree,
|
||||
OgnzUserTree,
|
||||
type SelectedOgnzUser,
|
||||
} from "@/widgets/ognz-user-tree";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -13,10 +19,7 @@ import {
|
||||
Input,
|
||||
} from "@common/components";
|
||||
|
||||
import { collectExpandedOgnzSnIds, filterOgnzUserTree } from "../lib/filter-ognz-user-tree";
|
||||
import type { ReviewerOption } from "../model/reviewer-option";
|
||||
|
||||
import { OgnzUserTree } from "./ognz-user-tree";
|
||||
import { fromSelectedOgnzUser, type ReviewerOption } from "../model/reviewer-option";
|
||||
|
||||
interface AprvrPickerDialogProps {
|
||||
open: boolean;
|
||||
@@ -29,6 +32,7 @@ function AprvrPickerDialog({ open, onOpenChange, onSelect }: AprvrPickerDialogPr
|
||||
const [displayTree, setDisplayTree] = useState<ReturnType<typeof filterOgnzUserTree>>([]);
|
||||
const [expandedOgnzSnIds, setExpandedOgnzSnIds] = useState<Set<number>>(new Set());
|
||||
const [selectedUserId, setSelectedUserId] = useState<string>();
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
|
||||
const { data, isPending, isError } = useOgnzUserTree({ enabled: open });
|
||||
|
||||
@@ -36,6 +40,7 @@ function AprvrPickerDialog({ open, onOpenChange, onSelect }: AprvrPickerDialogPr
|
||||
setKeyword("");
|
||||
setSelectedUserId(undefined);
|
||||
setExpandedOgnzSnIds(new Set());
|
||||
setIsSearchActive(false);
|
||||
setDisplayTree(data ?? []);
|
||||
}, [data]);
|
||||
|
||||
@@ -44,6 +49,7 @@ function AprvrPickerDialog({ open, onOpenChange, onSelect }: AprvrPickerDialogPr
|
||||
setKeyword("");
|
||||
setSelectedUserId(undefined);
|
||||
setExpandedOgnzSnIds(new Set());
|
||||
setIsSearchActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -66,15 +72,16 @@ function AprvrPickerDialog({ open, onOpenChange, onSelect }: AprvrPickerDialogPr
|
||||
const filtered = filterOgnzUserTree(data ?? [], keyword);
|
||||
setDisplayTree(filtered);
|
||||
setExpandedOgnzSnIds(collectExpandedOgnzSnIds(filtered));
|
||||
setIsSearchActive(keyword.trim().length > 0);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
resetState();
|
||||
};
|
||||
|
||||
const handleSelect = (reviewer: ReviewerOption) => {
|
||||
setSelectedUserId(reviewer.uuid);
|
||||
onSelect(reviewer);
|
||||
const handleSelect = (user: SelectedOgnzUser) => {
|
||||
setSelectedUserId(user.userUuid);
|
||||
onSelect(fromSelectedOgnzUser(user));
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
@@ -108,8 +115,10 @@ function AprvrPickerDialog({ open, onOpenChange, onSelect }: AprvrPickerDialogPr
|
||||
nodes={displayTree}
|
||||
isPending={isPending}
|
||||
isError={isError}
|
||||
isSearchActive={isSearchActive}
|
||||
expandedOgnzSnIds={expandedOgnzSnIds}
|
||||
selectedUserId={selectedUserId}
|
||||
showSelectButton
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { RvwMnpwMngPage } from "./ui/rvw-mnpw-mng-page";
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { RvwMnpwMngResponseDto } from "@/entities/rvw-mnpw";
|
||||
|
||||
import type { RvwMnpwBlockConfig } from "../model/rvw-mnpw-block-config";
|
||||
|
||||
export function groupRvwMnpwByAtrzSe(
|
||||
items: RvwMnpwMngResponseDto[],
|
||||
blocks: RvwMnpwBlockConfig[],
|
||||
): Record<string, RvwMnpwMngResponseDto[]> {
|
||||
const grouped = Object.fromEntries(
|
||||
blocks.map((block) => [block.atrzSeComCd, [] as RvwMnpwMngResponseDto[]]),
|
||||
);
|
||||
|
||||
for (const item of items) {
|
||||
grouped[item.atrzSeComCd]?.push(item);
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface RvwMnpwBlockConfig {
|
||||
id: string;
|
||||
title: string;
|
||||
atrzSeComCd: string;
|
||||
}
|
||||
|
||||
/** 결재선 관리 validators.ts ATRZ 코드와 동일. ATRZSE / ATRZSECD 배포 환경 코드표 최종 확인 필요 */
|
||||
export const RVW_MNPW_BLOCKS: RvwMnpwBlockConfig[] = [
|
||||
{ id: "receiver", title: "접수", atrzSeComCd: "ATRZ00" },
|
||||
// TBD: ATRZ02(검토2) 별도 블록 필요 여부 — 현재 ATRZ01만 검토 블록으로 사용
|
||||
{ id: "reviewer", title: "검토", atrzSeComCd: "ATRZ01" },
|
||||
{ id: "approver", title: "승인", atrzSeComCd: "ATRZ03" },
|
||||
{ id: "final", title: "최종승인", atrzSeComCd: "ATRZ99" },
|
||||
];
|
||||
|
||||
export const RVW_MNPW_PAGE_SIZE = 500;
|
||||
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { useOgnzUserTree } from "@/entities/user-organization";
|
||||
import {
|
||||
collectExpandedOgnzSnIds,
|
||||
filterOgnzUserTree,
|
||||
OgnzUserTree,
|
||||
type SelectedOgnzUser,
|
||||
} from "@/widgets/ognz-user-tree";
|
||||
import { Button, Input } from "@common/components";
|
||||
|
||||
interface OgnzPanelProps {
|
||||
selectedPerson: SelectedOgnzUser | null;
|
||||
onSelectPerson: (person: SelectedOgnzUser) => void;
|
||||
}
|
||||
|
||||
function OgnzPanel({ selectedPerson, onSelectPerson }: OgnzPanelProps) {
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [displayTree, setDisplayTree] = useState<ReturnType<typeof filterOgnzUserTree>>([]);
|
||||
const [expandedOgnzSnIds, setExpandedOgnzSnIds] = useState<Set<number>>(new Set());
|
||||
const [isSearchActive, setIsSearchActive] = useState(false);
|
||||
|
||||
const { data, isPending, isError } = useOgnzUserTree();
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayTree(data ?? []);
|
||||
}, [data]);
|
||||
|
||||
const handleToggle = useCallback((ognzSn: number, isExpanded: boolean) => {
|
||||
setExpandedOgnzSnIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (isExpanded) next.add(ognzSn);
|
||||
else next.delete(ognzSn);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
const filtered = filterOgnzUserTree(data ?? [], keyword);
|
||||
setDisplayTree(filtered);
|
||||
setExpandedOgnzSnIds(collectExpandedOgnzSnIds(filtered));
|
||||
setIsSearchActive(keyword.trim().length > 0);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setKeyword("");
|
||||
setDisplayTree(data ?? []);
|
||||
setExpandedOgnzSnIds(new Set());
|
||||
setIsSearchActive(false);
|
||||
};
|
||||
|
||||
const handleSelect = (person: SelectedOgnzUser) => {
|
||||
if (selectedPerson?.userUuid === person.userUuid) return;
|
||||
onSelectPerson(person);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 flex h-full flex-col pt-0">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">조직도</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2">
|
||||
<Input
|
||||
className="!text-[1.3rem]"
|
||||
placeholder="성명·부서 검색"
|
||||
value={keyword}
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
/>
|
||||
<Button type="submit">조회</Button>
|
||||
<Button type="button" color="white" onClick={handleReset}>
|
||||
초기화
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="min-h-[32rem] flex-1 overflow-auto">
|
||||
<OgnzUserTree
|
||||
nodes={displayTree}
|
||||
isPending={isPending}
|
||||
isError={isError}
|
||||
isSearchActive={isSearchActive}
|
||||
expandedOgnzSnIds={expandedOgnzSnIds}
|
||||
selectedUserId={selectedPerson?.userUuid}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { OgnzPanel };
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useRvwMnpwMngPage } from "@/entities/rvw-mnpw";
|
||||
import type { SelectedOgnzUser } from "@/widgets/ognz-user-tree";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import { groupRvwMnpwByAtrzSe } from "../lib/group-rvw-mnpw-by-atrz-se";
|
||||
import { RVW_MNPW_BLOCKS, RVW_MNPW_PAGE_SIZE } from "../model/rvw-mnpw-block-config";
|
||||
|
||||
import { RvwMnpwBlock } from "./rvw-mnpw-block";
|
||||
|
||||
interface RvwMnpwAsmtPanelProps {
|
||||
selectedPerson: SelectedOgnzUser | null;
|
||||
}
|
||||
|
||||
function RvwMnpwAsmtPanel({ selectedPerson }: RvwMnpwAsmtPanelProps) {
|
||||
const pageQuery = useRvwMnpwMngPage({
|
||||
page: 0,
|
||||
size: RVW_MNPW_PAGE_SIZE,
|
||||
sort: "atrzSeComCd,asc",
|
||||
});
|
||||
|
||||
const groupedItems = useMemo(
|
||||
() => groupRvwMnpwByAtrzSe(pageQuery.data?.content ?? [], RVW_MNPW_BLOCKS),
|
||||
[pageQuery.data?.content],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 flex h-full flex-col pt-0">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">인원 배치</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
{pageQuery.isPending ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<Skeleton className="h-[8rem]" />
|
||||
<Skeleton className="h-[8rem]" />
|
||||
<Skeleton className="h-[8rem]" />
|
||||
<Skeleton className="h-[8rem]" />
|
||||
</div>
|
||||
) : pageQuery.isError ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
인원 배치 목록을 불러오지 못했습니다.
|
||||
</p>
|
||||
) : (
|
||||
RVW_MNPW_BLOCKS.map((block) => (
|
||||
<RvwMnpwBlock
|
||||
key={block.id}
|
||||
block={block}
|
||||
items={groupedItems[block.atrzSeComCd] ?? []}
|
||||
selectedPerson={selectedPerson}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { RvwMnpwAsmtPanel };
|
||||
@@ -0,0 +1,111 @@
|
||||
"use client";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import type { RvwMnpwMngResponseDto } from "@/entities/rvw-mnpw";
|
||||
import { useDeleteRvwMnpwMng, useSaveRvwMnpwMng } from "@/entities/rvw-mnpw";
|
||||
import type { SelectedOgnzUser } from "@/widgets/ognz-user-tree";
|
||||
import { Badge, Button } from "@common/components";
|
||||
|
||||
import type { RvwMnpwBlockConfig } from "../model/rvw-mnpw-block-config";
|
||||
|
||||
interface RvwMnpwBlockProps {
|
||||
block: RvwMnpwBlockConfig;
|
||||
items: RvwMnpwMngResponseDto[];
|
||||
selectedPerson: SelectedOgnzUser | null;
|
||||
}
|
||||
|
||||
function RvwMnpwBlock({ block, items, selectedPerson }: RvwMnpwBlockProps) {
|
||||
const { confirm } = useConfirm();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const saveMutation = useSaveRvwMnpwMng();
|
||||
const deleteMutation = useDeleteRvwMnpwMng();
|
||||
|
||||
const handleAssign = async () => {
|
||||
if (!selectedPerson) return;
|
||||
|
||||
try {
|
||||
await saveMutation.mutateAsync({
|
||||
atrzSeComCd: block.atrzSeComCd,
|
||||
ognzSn: selectedPerson.ognzSn,
|
||||
userUuid: selectedPerson.userUuid,
|
||||
});
|
||||
alert("배치되었습니다.");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "배치에 실패했습니다.";
|
||||
alert(message);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemove = async (item: RvwMnpwMngResponseDto) => {
|
||||
if (!(await confirm("해당 인원을 배치에서 제거하시겠습니까?"))) return;
|
||||
|
||||
try {
|
||||
await deleteMutation.mutateAsync({
|
||||
atrzSeComCd: item.atrzSeComCd,
|
||||
ognzSn: item.ognzSn,
|
||||
userUuid: item.userUuid,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "제거에 실패했습니다.";
|
||||
alert(message);
|
||||
}
|
||||
};
|
||||
|
||||
const isAssigning = saveMutation.isPending;
|
||||
const isRemoving = deleteMutation.isPending;
|
||||
|
||||
return (
|
||||
<section className="rounded-md border border-border bg-background p-4">
|
||||
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="text-xl font-semibold">{block.title}</h4>
|
||||
<Badge>{items.length}</Badge>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="green"
|
||||
disabled={!selectedPerson || isAssigning}
|
||||
onClick={handleAssign}
|
||||
>
|
||||
선택 인원 배치
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{items.length === 0 ? (
|
||||
<p className="py-4 text-center text-lg text-muted-foreground">배치된 인원이 없습니다.</p>
|
||||
) : (
|
||||
<ul className="grid grid-cols-4 gap-2">
|
||||
{items.map((item) => (
|
||||
<li
|
||||
key={`${item.atrzSeComCd}-${item.ognzSn}-${item.userUuid}`}
|
||||
className="flex items-center justify-between gap-3 rounded-sm border border-border px-3 py-2"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* TBD: userUuidNm @CodeName 미적용 시 백엔드 enrichUserNames 결과만 표시 */}
|
||||
<p className="truncate text-[1.4rem] font-medium text-label">
|
||||
{item.userUuidNm || item.userUuid}
|
||||
</p>
|
||||
{/* TBD: 응답 DTO에 부서·직급 필드 없음 — ognzSn만 표시 */}
|
||||
{/* <p className="text-[1.3rem] text-muted-foreground">조직 SN: {item.ognzSn}</p> */}
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
color="white"
|
||||
disabled={isRemoving}
|
||||
onClick={() => handleRemove(item)}
|
||||
>
|
||||
제거
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { RvwMnpwBlock };
|
||||
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import type { SelectedOgnzUser } from "@/widgets/ognz-user-tree";
|
||||
|
||||
import { OgnzPanel } from "./ognz-panel";
|
||||
import { RvwMnpwAsmtPanel } from "./rvw-mnpw-asmt-panel";
|
||||
|
||||
function RvwMnpwMngPage() {
|
||||
const [selectedPerson, setSelectedPerson] = useState<SelectedOgnzUser | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 px-[1.8rem] pb-6">
|
||||
{/* TBD: 역할별 CRUD 권한 — 메뉴·API 권한 연동 후 버튼 비활성 처리 */}
|
||||
<div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(280px,1fr)_minmax(0,2.5fr)]">
|
||||
<OgnzPanel selectedPerson={selectedPerson} onSelectPerson={setSelectedPerson} />
|
||||
<RvwMnpwAsmtPanel selectedPerson={selectedPerson} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { RvwMnpwMngPage };
|
||||
@@ -0,0 +1,4 @@
|
||||
export { collectExpandedOgnzSnIds, filterOgnzUserTree } from "./lib/filter-ognz-user-tree";
|
||||
export { type SelectedOgnzUser, toSelectedOgnzUser } from "./model/selected-ognz-user";
|
||||
export { OgnzUserTree } from "./ui/ognz-user-tree";
|
||||
export { OgnzUserTreeNode } from "./ui/ognz-user-tree-node";
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { OgnzUserTreeResponseDto, OgnzUserTreeUserDto } from "@/entities/user-organization";
|
||||
|
||||
export interface SelectedOgnzUser {
|
||||
userUuid: string;
|
||||
userNm: string;
|
||||
ognzSn: number;
|
||||
ognzNm: string;
|
||||
jbgdSeCd?: string;
|
||||
jbpsSeCd?: string;
|
||||
}
|
||||
|
||||
export function toSelectedOgnzUser(
|
||||
user: OgnzUserTreeUserDto,
|
||||
node: Pick<OgnzUserTreeResponseDto, "ognzSn" | "ognzNm">,
|
||||
): SelectedOgnzUser {
|
||||
return {
|
||||
userUuid: user.userUnqId,
|
||||
userNm: user.userNm,
|
||||
ognzSn: node.ognzSn,
|
||||
ognzNm: node.ognzNm,
|
||||
jbgdSeCd: user.jbgdSeCd || undefined,
|
||||
jbpsSeCd: user.jbpsSeCd || undefined,
|
||||
};
|
||||
}
|
||||
+20
-15
@@ -11,16 +11,16 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from "@common/components";
|
||||
|
||||
import type { ReviewerOption } from "../model/reviewer-option";
|
||||
import { toReviewerOption } from "../model/reviewer-option";
|
||||
import { type SelectedOgnzUser, toSelectedOgnzUser } from "../model/selected-ognz-user";
|
||||
|
||||
interface OgnzUserTreeNodeProps {
|
||||
node: OgnzUserTreeResponseDto;
|
||||
depth?: number;
|
||||
expandedOgnzSnIds: Set<number>;
|
||||
selectedUserId?: string;
|
||||
showSelectButton?: boolean;
|
||||
onToggle: (ognzSn: number, open: boolean) => void;
|
||||
onSelect: (reviewer: ReviewerOption) => void;
|
||||
onSelect: (user: SelectedOgnzUser) => void;
|
||||
}
|
||||
|
||||
function OgnzUserTreeNode({
|
||||
@@ -28,6 +28,7 @@ function OgnzUserTreeNode({
|
||||
depth = 0,
|
||||
expandedOgnzSnIds,
|
||||
selectedUserId,
|
||||
showSelectButton = false,
|
||||
onToggle,
|
||||
onSelect,
|
||||
}: OgnzUserTreeNodeProps) {
|
||||
@@ -71,6 +72,7 @@ function OgnzUserTreeNode({
|
||||
<CollapsibleContent>
|
||||
{node.users.map((user) => {
|
||||
const isSelected = selectedUserId === user.userUnqId;
|
||||
const selectedUser = toSelectedOgnzUser(user, node);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -78,21 +80,23 @@ function OgnzUserTreeNode({
|
||||
style={{ paddingInlineStart: indent + 24 }}
|
||||
className={cn(
|
||||
"group flex cursor-pointer items-center justify-between gap-2 rounded-sm p-2 hover:bg-accent",
|
||||
isSelected && "bg-accent",
|
||||
isSelected && "!border !border-primary !bg-primary-transparent",
|
||||
)}
|
||||
onClick={() => onSelect(toReviewerOption(user, node.ognzNm))}
|
||||
onClick={() => onSelect(selectedUser)}
|
||||
>
|
||||
<span className="text-[1.4rem] text-label">{user.userNm}</span>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect(toReviewerOption(user, node.ognzNm));
|
||||
}}
|
||||
>
|
||||
선택
|
||||
</Button>
|
||||
{showSelectButton ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect(selectedUser);
|
||||
}}
|
||||
>
|
||||
선택
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -104,6 +108,7 @@ function OgnzUserTreeNode({
|
||||
depth={depth + 1}
|
||||
expandedOgnzSnIds={expandedOgnzSnIds}
|
||||
selectedUserId={selectedUserId}
|
||||
showSelectButton={showSelectButton}
|
||||
onToggle={onToggle}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
+8
-3
@@ -3,7 +3,7 @@
|
||||
import type { OgnzUserTreeResponseDto } from "@/entities/user-organization";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import type { ReviewerOption } from "../model/reviewer-option";
|
||||
import type { SelectedOgnzUser } from "../model/selected-ognz-user";
|
||||
|
||||
import { OgnzUserTreeNode } from "./ognz-user-tree-node";
|
||||
|
||||
@@ -11,18 +11,22 @@ interface OgnzUserTreeProps {
|
||||
nodes: OgnzUserTreeResponseDto[];
|
||||
isPending?: boolean;
|
||||
isError?: boolean;
|
||||
isSearchActive?: boolean;
|
||||
expandedOgnzSnIds: Set<number>;
|
||||
selectedUserId?: string;
|
||||
showSelectButton?: boolean;
|
||||
onToggle: (ognzSn: number, open: boolean) => void;
|
||||
onSelect: (reviewer: ReviewerOption) => void;
|
||||
onSelect: (user: SelectedOgnzUser) => void;
|
||||
}
|
||||
|
||||
function OgnzUserTree({
|
||||
nodes,
|
||||
isPending,
|
||||
isError,
|
||||
isSearchActive = false,
|
||||
expandedOgnzSnIds,
|
||||
selectedUserId,
|
||||
showSelectButton = false,
|
||||
onToggle,
|
||||
onSelect,
|
||||
}: OgnzUserTreeProps) {
|
||||
@@ -47,7 +51,7 @@ function OgnzUserTree({
|
||||
if (nodes.length === 0) {
|
||||
return (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">
|
||||
등록된 조직/사용자가 없습니다.
|
||||
{isSearchActive ? "조회된 인원이 없습니다." : "등록된 조직/사용자가 없습니다."}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -60,6 +64,7 @@ function OgnzUserTree({
|
||||
node={node}
|
||||
expandedOgnzSnIds={expandedOgnzSnIds}
|
||||
selectedUserId={selectedUserId}
|
||||
showSelectButton={showSelectButton}
|
||||
onToggle={onToggle}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
Reference in New Issue
Block a user