Files
frontend/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-rvw-rslt-inq-dialog.tsx
T
2026-10-11 13:16:56 +09:00

225 lines
6.3 KiB
TypeScript

"use client";
import { useState } from "react";
import {
type PwrkCnsltnCnsltnResponseDto,
usePwrkCnsltnMngByUpDocNo,
} from "@/entities/pwrk-cnsltn-mng";
import {
Button,
Checkbox,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Skeleton,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@common/components";
interface PwrkCnsltnRvwRsltInqDialogProps {
open: boolean;
fnrsCnsltnDocNo: string;
isSubmitting?: boolean;
onClose: () => void;
onConfirm: (selected: PwrkCnsltnCnsltnResponseDto[]) => void;
}
interface CnsltnListTableProps {
rows: PwrkCnsltnCnsltnResponseDto[];
selectedRowKeys: Set<string>;
onToggleAll: (checked: boolean) => void;
onToggleRow: (rowKey: string, checked: boolean) => void;
}
function PwrkCnsltnRvwRsltInqDialog({
open,
fnrsCnsltnDocNo,
isSubmitting = false,
onClose,
onConfirm,
}: PwrkCnsltnRvwRsltInqDialogProps) {
const [selectedRowKeys, setSelectedRowKeys] = useState<Set<string>>(new Set());
const {
data: pwrkCnsltnData,
isLoading,
isError,
} = usePwrkCnsltnMngByUpDocNo(fnrsCnsltnDocNo, open);
const cnsltnList = pwrkCnsltnData?.cnsltnList ?? [];
const selectedRows = cnsltnList.filter((row, index) =>
selectedRowKeys.has(getCnsltnRowKey(row, index)),
);
function handleClose() {
setSelectedRowKeys(new Set());
onClose();
}
function handleOpenChange(nextOpen: boolean) {
if (!nextOpen) handleClose();
}
function handleToggleAll(checked: boolean) {
if (!checked) {
setSelectedRowKeys(new Set());
return;
}
setSelectedRowKeys(new Set(cnsltnList.map((row, index) => getCnsltnRowKey(row, index))));
}
function handleToggleRow(rowKey: string, checked: boolean) {
setSelectedRowKeys((prev) => {
const next = new Set(prev);
if (checked) next.add(rowKey);
else next.delete(rowKey);
return next;
});
}
function handleConfirm() {
if (selectedRows.length === 0) return;
onConfirm(selectedRows);
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent
className="max-h-[96vh] max-w-[120rem] overflow-y-auto"
aria-describedby={undefined}
>
<DialogHeader>
<DialogTitle>검토 결과 조회</DialogTitle>
</DialogHeader>
{isLoading ? (
<div className="space-y-3">
<Skeleton className="h-16 w-full" />
<Skeleton className="h-64 w-full" />
</div>
) : null}
{isError ? (
<p className="py-16 text-center text-lg text-destructive">
실무협의 검토결과를 불러오지 못했습니다.
</p>
) : null}
{!isLoading && !isError && pwrkCnsltnData ? (
<div className="h-[64rem] overflow-x-auto">
{cnsltnList.length === 0 ? (
<p className="py-10 text-center text-muted-foreground">조회된 검토의견이 없습니다.</p>
) : (
<CnsltnListTable
rows={cnsltnList}
selectedRowKeys={selectedRowKeys}
onToggleAll={handleToggleAll}
onToggleRow={handleToggleRow}
/>
)}
</div>
) : null}
<DialogFooter>
<Button type="button" color="white" onClick={handleClose} disabled={isSubmitting}>
닫기
</Button>
<Button
type="button"
onClick={handleConfirm}
disabled={isSubmitting || selectedRows.length === 0}
>
보완요청
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
function CnsltnListTable({
rows,
selectedRowKeys,
onToggleAll,
onToggleRow,
}: CnsltnListTableProps) {
const selectedCount = rows.filter((row, index) =>
selectedRowKeys.has(getCnsltnRowKey(row, index)),
).length;
const isAllSelected = rows.length > 0 && selectedCount === rows.length;
const isSomeSelected = selectedCount > 0 && !isAllSelected;
return (
<Table caption="검토 결과 조회 목록으로 선택, 구분, 검토의견 정보 제공" variant="secondary">
<colgroup>
<col width="4%" />
<col width="4%" />
<col width="92%" />
</colgroup>
<TableHeader className="sticky top-0">
<TableRow>
<TableHead className="w-14" align="center">
<Checkbox
checked={isAllSelected ? true : isSomeSelected ? "indeterminate" : false}
onCheckedChange={(checked) => onToggleAll(checked === true)}
aria-label="전체 선택"
/>
</TableHead>
<TableHead>구분</TableHead>
<TableHead>검토의견</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((row, index) => {
const rowKey = getCnsltnRowKey(row, index);
const isSelected = selectedRowKeys.has(rowKey);
return (
<TableRow
key={rowKey}
data-state={isSelected ? "selected" : undefined}
className={isSelected ? "!bg-primary-transparent" : undefined}
>
<TableCell align="center">
<Checkbox
checked={isSelected}
onCheckedChange={(checked) => onToggleRow(rowKey, checked === true)}
aria-label="행 선택"
/>
</TableCell>
<TableCell align="center">
<p className="whitespace-pre-wrap">{formatOptional(row.rvwOpnnSn)}</p>
</TableCell>
<TableCell>
<p className="whitespace-pre-wrap">{formatOptional(row.rvwOpnn)}</p>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
}
function getCnsltnRowKey(row: PwrkCnsltnCnsltnResponseDto, index: number) {
return [row.pwrkCnsltnDocNo, row.verSn, row.dtlSn, row.rvwOpnnSn, String(index)]
.filter((value) => value !== undefined && value !== null && value !== "")
.join("::");
}
function formatOptional(value: string | number | undefined | null) {
if (value === undefined || value === null) return "-";
const text = String(value).trim();
return text ? text : "-";
}
export { PwrkCnsltnRvwRsltInqDialog };
export type { PwrkCnsltnRvwRsltInqDialogProps };