121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import { AxiosError } from "axios";
|
|
|
|
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";
|
|
|
|
/** 서버 오류 응답의 message 를 우선 표시하고, 없으면 기본 문구를 사용한다 */
|
|
function getErrorMessage(error: unknown, fallback: string) {
|
|
if (error instanceof AxiosError) {
|
|
return error.response?.data?.message || fallback;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
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) {
|
|
alert(getErrorMessage(error, "배치에 실패했습니다."));
|
|
}
|
|
};
|
|
|
|
const handleRemove = async (item: RvwMnpwMngResponseDto) => {
|
|
if (!(await confirm("해당 인원을 배치에서 제거하시겠습니까?"))) return;
|
|
|
|
try {
|
|
await deleteMutation.mutateAsync({
|
|
atrzSeComCd: item.atrzSeComCd,
|
|
ognzSn: item.ognzSn,
|
|
userUuid: item.userUuid,
|
|
});
|
|
} catch (error) {
|
|
alert(getErrorMessage(error, "제거에 실패했습니다."));
|
|
}
|
|
};
|
|
|
|
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 color="gray">{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="flex min-w-0 flex-1 items-center gap-2">
|
|
<p className="text-[1.4rem] font-medium text-label">
|
|
{item.userUuidNm || item.userUuid}
|
|
</p>
|
|
{item.ognzNm && item.jbpsSeNm && (
|
|
<span className="text-lg text-muted-foreground">
|
|
({item.ognzNm} · {item.jbpsSeNm})
|
|
</span>
|
|
)}
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
size="sm"
|
|
color="white"
|
|
disabled={isRemoving}
|
|
onClick={() => handleRemove(item)}
|
|
>
|
|
제거
|
|
</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export { RvwMnpwBlock };
|