225 lines
6.3 KiB
TypeScript
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 };
|