Merge remote-tracking branch 'origin/iipbs/as/dev' into iipbs/as/dev

This commit is contained in:
2026-05-22 14:36:46 +09:00
5 changed files with 79 additions and 37 deletions
@@ -15,11 +15,17 @@ export function validateAtrzOrder(rows: ElaprAplnRow[]): string | null {
const visibleRows = getVisibleRows(rows);
const receiverCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_RECEIVER_CD).length;
if (receiverCount === 0) {
return "접수자는 반드시 등록해야 합니다.";
}
if (receiverCount > 1) {
return "접수자는 한 명만 등록할 수 있습니다.";
}
const finalCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_FINAL_CD).length;
if (finalCount === 0) {
return "최종승인자는 반드시 등록해야 합니다.";
}
if (finalCount > 1) {
return "최종승인자는 한 명만 등록할 수 있습니다.";
}
@@ -98,7 +98,6 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) {
};
const handleRowDelete = async (row: ElaprAplnRow) => {
if (!(await confirm("선택한 결재선을 삭제하시겠습니까?"))) return;
markDeleted(row.rowKey);
};
@@ -129,6 +128,8 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) {
return;
}
if (!(await confirm("결재선을 저장하시겠습니까?"))) return;
await saveMultiMutation.mutateAsync({ body: payload, elaprTmpltId });
alert("결재선이 저장되었습니다.");
await aplnQuery.refetch();
@@ -158,12 +159,7 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) {
</Button>
<Button
type="button"
disabled={
isDisabled ||
isEditingLocked ||
!canSave ||
saveMultiMutation.isPending
}
disabled={isDisabled || isEditingLocked || !canSave || saveMultiMutation.isPending}
onClick={handleSaveAll}
>
결재선 저장
@@ -6,6 +6,9 @@ import {
DialogContent,
DialogHeader,
DialogTitle,
Label,
RadioGroup,
RadioGroupItem,
Table,
TableBody,
TableCell,
@@ -14,7 +17,7 @@ import {
TableRow,
} from "@common/components";
import { buildRvwDisplayRows, formatRvwCnLvNo, formatTreeBranchPrefix } from "../model/build-rvw-display-rows";
import { buildRvwDisplayRows, formatTreeBranchPrefix } from "../model/build-rvw-display-rows";
import type { CklstRvwRow } from "../model/cklst-rvw-row-types";
interface CklstPreviewDialogProps {
@@ -49,36 +52,48 @@ function CklstPreviewDialog({
<p className="py-6 text-center text-muted-foreground">표시할 항목이 없습니다.</p>
) : (
<Table>
<colgroup>
<col width="25%" />
<col width="50%" />
<col width="25%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>점검항목</TableHead>
<TableHead>주요 검토내용</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead align="left">점검항목</TableHead>
<TableHead align="left">주요 검토내용</TableHead>
<TableHead>검토 여부</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => {
const branchPrefix = formatTreeBranchPrefix(depth);
if (row.useYn === "N") return null;
return (
<TableRow key={row.rowKey}>
<TableCell>
<span className="inline-flex items-center gap-1">
{branchPrefix ? (
<span className="font-mono text-muted-foreground">{branchPrefix}</span>
) : null}
<span>{getArtclLabel(row.cklstArtclSn)}</span>
</span>
</TableCell>
<TableCell>{row.rvwCn ?? "—"}</TableCell>
<TableCell align="center">
{row.useYn === "Y" ? "예" : row.useYn === "N" ? "아니오" : "—"}
</TableCell>
<TableCell align="center">
{formatRvwCnLvNo(row.rvwCnLvNo)}
</TableCell>
</TableRow>
<TableRow key={row.rowKey}>
<TableCell align="left">
<span className="inline-flex items-center gap-1">
{branchPrefix ? (
<span className="font-mono text-muted">{branchPrefix}</span>
) : null}
<span>{getArtclLabel(row.cklstArtclSn)}</span>
</span>
</TableCell>
<TableCell>{row.rvwCn ?? "—"}</TableCell>
<TableCell align="center">
<RadioGroup className="flex items-center justify-center">
<div className="flex items-center gap-1">
<RadioGroupItem value="Y" id={`${row.rowKey}-use-y`} />
<Label htmlFor={`${row.rowKey}-use-y`}>예</Label>
</div>
<div className="flex items-center gap-1">
<RadioGroupItem value="N" id={`${row.rowKey}-use-n`} />
<Label htmlFor={`${row.rowKey}-use-n`}>아니오</Label>
</div>
</RadioGroup>
</TableCell>
</TableRow>
);
})}
</TableBody>
@@ -155,17 +155,31 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
);
const handleDelete = async (row: CklstRvwRow) => {
if (!(await confirm("선택한 항목을 삭제하시겠습니까?"))) return;
if (
!(await confirm(
"선택한 항목을 삭제하시겠습니까?\n삭제한 항목은 되돌릴 수 없으며, 하위 항목까지 모두 삭제됩니다.",
))
)
return;
if (row.isNew) {
removeRowLocal(row.rowKey);
return;
}
if (row.cklstRvwSn == null) return;
const children = rows.filter((r) => r.upCklstRvwSn === row.cklstRvwSn);
if (children.length > 0) {
for (const child of children) {
await deleteMutation.mutateAsync({
cklstRvwSn: child.cklstRvwSn!,
cklstArtclSn: child.cklstArtclSn,
cklstSn: child.cklstSn,
});
}
}
await deleteMutation.mutateAsync({
cklstRvwSn: row.cklstRvwSn,
cklstRvwSn: row.cklstRvwSn!,
cklstArtclSn: row.cklstArtclSn,
cklstSn: row.cklstSn,
});
@@ -184,6 +198,8 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
return;
}
if (!(await confirm("체크리스트 항목을 저장하시겠습니까?"))) return;
if (row.isNew) {
await createMutation.mutateAsync({
cklstSn: row.cklstSn,
@@ -233,9 +249,13 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
미리보기
</Button>
<Button type="button" color="white" disabled={isDisabled} onClick={addRootRow}>
추가
+ 체크리스트 추가
</Button>
<Button type="button" disabled={isDisabled || isSaving} onClick={handleSave}>
<Button
type="button"
disabled={getDirtyRows().length === 0 || isDisabled || isSaving}
onClick={handleSave}
>
저장
</Button>
</div>
@@ -248,7 +268,7 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
</div>
) : isDisabled ? (
<p className="py-8 text-center text-lg text-muted-foreground">
상단에서 점검표를 선택하면 체크리스트 항목을 편집할 수 있습니다.
점검표를 선택하면 체크리스트 항목을 편집할 수 있습니다.
</p>
) : displayRows.length === 0 ? (
<p className="py-8 text-center text-lg text-muted-foreground">
@@ -16,7 +16,11 @@ import {
TableRow,
} from "@common/components";
import { canAddChildRow, formatRvwCnLvNo, formatTreeBranchPrefix } from "../model/build-rvw-display-rows";
import {
canAddChildRow,
formatRvwCnLvNo,
formatTreeBranchPrefix,
} from "../model/build-rvw-display-rows";
import type { CklstRvwRow } from "../model/cklst-rvw-row-types";
interface CklstRvwRowProps {
@@ -44,11 +48,12 @@ function CklstRvwRowComponent({
<TableCell>
<div className="flex min-w-[12rem] items-center gap-1">
{branchPrefix ? (
<span className="shrink-0 font-mono text-muted-foreground">{branchPrefix}</span>
<span className="shrink-0 font-mono text-muted">{branchPrefix}</span>
) : null}
<Select
value={row.cklstArtclSn ? String(row.cklstArtclSn) : ""}
onValueChange={(v) => onChange({ cklstArtclSn: Number(v), isDirty: true })}
disabled={!row.isNew}
>
<SelectTrigger className="w-full flex-1">
<SelectValue placeholder="점검항목 선택" />