Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f816bf005e | ||
|
|
791ad6ce37 | ||
|
|
4bc802cee2 | ||
|
|
4662c8c896 | ||
|
|
7058560a03 | ||
|
|
79a5999ad5 | ||
|
|
1aeded5da2 | ||
|
|
e8d1d882c2 | ||
|
|
39dca810a5 | ||
|
|
97f94688f1 | ||
|
|
0b502cbc99 | ||
|
|
a8eb8f9ab1 | ||
|
|
6471f4365a | ||
|
|
48999f5699 | ||
|
|
5ad64a596b | ||
|
|
df60bd0b40 | ||
|
|
41e6f1cf48 | ||
|
|
0802ba191a | ||
|
|
ad57ed241d | ||
|
|
f17cb420e3 | ||
|
|
c2396f093b | ||
|
|
bdf65dcfd1 | ||
|
|
2aefbe17ee | ||
|
|
5e43d69d8e | ||
|
|
4482b92ccd | ||
|
|
d3c39caefb | ||
|
|
24c5dce24d | ||
|
|
9971e512c8 | ||
|
|
0c77ed9bba | ||
|
|
e759c96ade | ||
|
|
f602b75268 | ||
|
|
d4d9eaf773 | ||
|
|
11d0378060 | ||
|
|
8ed4f959cb | ||
|
|
e5f7778861 | ||
|
|
2d086a05b2 | ||
|
|
6ef4603a7c |
@@ -4,7 +4,7 @@ import { Providers } from "@/app/providers";
|
||||
import "@/app/styles/globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "행정지원시스템",
|
||||
title: "유역하수도통합플랫폼 Hasuon",
|
||||
description: "행정지원시스템",
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@/app/styles/globals.css";
|
||||
|
||||
export const metadata = {
|
||||
title: "디지털 행정지원 시스템",
|
||||
title: "유역하수도통합플랫폼 Hasuon",
|
||||
};
|
||||
|
||||
export default function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Providers } from "@/app/providers";
|
||||
import "@/app/styles/globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "디지털 행정지원 시스템",
|
||||
title: "유역하수도통합플랫폼 Hasuon",
|
||||
description: "디지털 행정지원 시스템",
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import Image from "next/image";
|
||||
import "@/app/styles/globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "디지털 행정지원 시스템",
|
||||
title: "유역하수도통합플랫폼 Hasuon",
|
||||
description: "페이지가 존재하지 않습니다. 관리자에게 문의하여 주십시오.",
|
||||
};
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export function BaseLayoutWrapper({ children }: BaseLayoutWrapperProps) {
|
||||
authService={authService}
|
||||
currentPath={pathname ?? ""}
|
||||
handleMenuClick={handleMenuClick}
|
||||
sidebarTriggerPlacement="lnb"
|
||||
>
|
||||
<PageLayout>{children}</PageLayout>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -16,9 +16,20 @@ interface CodeSelectProps extends React.ComponentPropsWithoutRef<typeof Select>
|
||||
useYn?: string;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
/** 레이블(label htmlFor) 연결용 트리거 id */
|
||||
id?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
function CodeSelect({ upCd, useYn, placeholder = "선택", className, ...props }: CodeSelectProps) {
|
||||
function CodeSelect({
|
||||
upCd,
|
||||
useYn,
|
||||
placeholder = "선택",
|
||||
className,
|
||||
id,
|
||||
title,
|
||||
...props
|
||||
}: CodeSelectProps) {
|
||||
const listQuery = useCodeList(useYn ? undefined : upCd);
|
||||
const listByUseYnQuery = useCodeListByUseYn(useYn ? upCd : undefined, useYn ?? "");
|
||||
|
||||
@@ -27,7 +38,7 @@ function CodeSelect({ upCd, useYn, placeholder = "선택", className, ...props }
|
||||
|
||||
return (
|
||||
<Select {...props} disabled={props.disabled || isLoading}>
|
||||
<SelectTrigger className={cn("w-[12rem]", className)}>
|
||||
<SelectTrigger id={id} title={title} className={cn("w-[12rem]", className)}>
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -76,7 +76,7 @@ function AprvrPickerDialog({ open, onOpenChange, atrzSeComCd, onSelect }: AprvrP
|
||||
배치된 검토인력이 없습니다.
|
||||
</p>
|
||||
) : (
|
||||
<Table className="w-full">
|
||||
<Table caption="검토인력 선택 목록으로 성명, 부서, 직위, 직급, 선택 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="25%" />
|
||||
<col width="25%" />
|
||||
|
||||
@@ -162,7 +162,7 @@ function ElaprAplnPickerGrid({
|
||||
</p>
|
||||
) : (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<Table className="w-full">
|
||||
<Table caption="결재선 관리 목록으로 결재순서, 결재구분, 결재자, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
|
||||
@@ -315,7 +315,7 @@ function ElaprApprovalDialog({
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<Table className="w-full">
|
||||
<Table caption="결재 진행 단계 목록으로 결재순서, 결재구분, 결재자, 전결자 지정, 결재상태, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="8%" />
|
||||
<col width="8%" />
|
||||
|
||||
@@ -66,6 +66,7 @@ function ElaprTmpltSelectTable({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "전자결재서식 목록으로 결재서식명 정보 제공",
|
||||
onRowClick: (row) => {
|
||||
setRowSelection({ [row.elaprTmpltId]: true });
|
||||
onSelectTmplt(row);
|
||||
|
||||
@@ -73,7 +73,7 @@ function AprvrPickerDialog({
|
||||
배치된 검토인력이 없습니다.
|
||||
</p>
|
||||
) : (
|
||||
<Table className="w-full">
|
||||
<Table caption="검토인력 선택 목록으로 성명, 부서, 직위, 직급, 선택 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="25%" />
|
||||
<col width="25%" />
|
||||
|
||||
@@ -185,7 +185,7 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) {
|
||||
</p>
|
||||
) : (
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<Table className="w-full">
|
||||
<Table caption="결재선 관리 목록으로 결재순서, 결재구분, 결재자 ID, 결재자, 등록일시, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="5%" />
|
||||
<col width="10%" />
|
||||
|
||||
@@ -90,6 +90,7 @@ function ElaprTmpltList({ selectedTmpltId, onSelectTmplt }: ElaprTmpltListProps)
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "전자결재선 관리 목록으로 결재서식명, 문서구분, 등록일시, 관리 정보 제공",
|
||||
onRowClick: (row) => {
|
||||
setRowSelection({ [row.elaprTmpltId]: true });
|
||||
onSelectTmplt(row);
|
||||
|
||||
@@ -84,9 +84,9 @@ function CklstArtclFormDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>사용여부</Label>
|
||||
<Label htmlFor="cklstArtclUseYn">사용여부</Label>
|
||||
<Select value={useYn} onValueChange={(v) => setValue("useYn", v as "Y" | "N")}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectTrigger id="cklstArtclUseYn" className="w-full">
|
||||
<SelectValue placeholder="사용여부" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -67,7 +67,12 @@ function CklstArtclList({ cklstSn }: CklstArtclListProps) {
|
||||
</div>
|
||||
),
|
||||
Empty: <NoData />,
|
||||
Success: () => <DataTable table={table} />,
|
||||
Success: () => (
|
||||
<DataTable
|
||||
table={table}
|
||||
caption="점검항목 관리 목록으로 점검항목코드, 점검항목명, 사용여부, 등록일시, 관리 정보 제공"
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
const handleFormSubmit = async (values: CklstArtclFormValues) => {
|
||||
|
||||
@@ -100,8 +100,9 @@ function CklstMngFormDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>문서구분</Label>
|
||||
<Label htmlFor="cklstDocSeComCd">문서구분</Label>
|
||||
<CodeSelect
|
||||
id="cklstDocSeComCd"
|
||||
upCd="ASDOCSE"
|
||||
value={docSeComCd}
|
||||
onValueChange={(v) => setValue("docSeComCd", v)}
|
||||
@@ -110,8 +111,9 @@ function CklstMngFormDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>업무구분</Label>
|
||||
<Label htmlFor="cklstTaskSeComCd">업무구분</Label>
|
||||
<CodeSelect
|
||||
id="cklstTaskSeComCd"
|
||||
upCd={docSeComCd || "ASDOCSE"}
|
||||
value={watch("taskSeComCd")}
|
||||
onValueChange={(v) => setValue("taskSeComCd", v)}
|
||||
@@ -121,9 +123,9 @@ function CklstMngFormDialog({
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>사용여부</Label>
|
||||
<Label htmlFor="cklstUseYn">사용여부</Label>
|
||||
<Select value={useYn} onValueChange={(v) => setValue("useYn", v as "Y" | "N")}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectTrigger id="cklstUseYn" className="w-full">
|
||||
<SelectValue placeholder="사용여부" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -83,6 +83,7 @@ function CklstMngList({ selectedCklstSn, onSelectCklst }: CklstMngListProps) {
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "점검표 관리 목록으로 점검표일련번호, 점검표명, 문서구분, 업무구분, 사용여부, 등록일시, 관리 정보 제공",
|
||||
onRowClick: (row) => {
|
||||
setRowSelection({ [String(row.cklstSn)]: true });
|
||||
onSelectCklst(row);
|
||||
|
||||
@@ -44,65 +44,78 @@ function CklstPreviewDialog({
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[64rem]">
|
||||
<DialogContent className="flex max-h-[90vh] max-w-[64rem] flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>체크리스트 미리보기 — {cklstNm}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{displayRows.length === 0 ? (
|
||||
<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 align="left">점검항목</TableHead>
|
||||
<TableHead align="left">주요 검토내용</TableHead>
|
||||
<TableHead>검토 여부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{displayRows.map(({ row, depth }) => {
|
||||
const branchPrefix = formatTreeBranchPrefix(depth);
|
||||
const showRadios = row.bchckUseYn !== "N";
|
||||
// 항목이 많아 창 높이를 넘으면 표 영역만 스크롤 (초점을 받아 키보드로도 스크롤 가능)
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="region"
|
||||
aria-label="체크리스트 미리보기 목록"
|
||||
className="min-h-0 flex-1 overflow-y-auto rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
||||
>
|
||||
<Table
|
||||
caption={`체크리스트 미리보기 ${cklstNm} 목록으로 점검항목, 주요 검토내용, 검토 여부 정보 제공`}
|
||||
>
|
||||
<colgroup>
|
||||
<col width="25%" />
|
||||
<col width="50%" />
|
||||
<col width="25%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead align="left">점검항목</TableHead>
|
||||
<TableHead align="left">주요 검토내용</TableHead>
|
||||
<TableHead>검토 여부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{displayRows.map(({ row, depth }) => {
|
||||
const branchPrefix = formatTreeBranchPrefix(depth);
|
||||
const showRadios = row.bchckUseYn !== "N";
|
||||
|
||||
if (row.useYn === "N") return null;
|
||||
if (row.useYn === "N") return null;
|
||||
|
||||
return (
|
||||
<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">
|
||||
{showRadios ? (
|
||||
<RadioGroup className="flex items-center justify-center">
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="Y" id={`${row.rowKey}-preview-y`} />
|
||||
<Label htmlFor={`${row.rowKey}-preview-y`}>예</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="N" id={`${row.rowKey}-preview-n`} />
|
||||
<Label htmlFor={`${row.rowKey}-preview-n`}>아니오</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
return (
|
||||
<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">
|
||||
{showRadios ? (
|
||||
<RadioGroup
|
||||
aria-label="검토 여부"
|
||||
className="flex items-center justify-center"
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="Y" id={`${row.rowKey}-preview-y`} />
|
||||
<Label htmlFor={`${row.rowKey}-preview-y`}>예</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<RadioGroupItem value="N" id={`${row.rowKey}-preview-n`} />
|
||||
<Label htmlFor={`${row.rowKey}-preview-n`}>아니오</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -277,7 +277,7 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
|
||||
등록된 항목이 없습니다. [추가]로 1단계 항목을 등록하세요.
|
||||
</p>
|
||||
) : (
|
||||
<Table className="w-full">
|
||||
<Table caption="체크리스트 항목 목록으로 점검항목, 주요 검토내용, 사용 여부, 체크 사용, 정렬 순서, 구분, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="16%" />
|
||||
<col width="36%" />
|
||||
@@ -289,13 +289,13 @@ function CklstRvwGrid({ selectedCklst }: CklstRvwGridProps) {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>점검항목</TableHead>
|
||||
<TableHead>주요 검토내용</TableHead>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead>체크 사용</TableHead>
|
||||
<TableHead>정렬 순서</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
<TableHead scope="col">점검항목</TableHead>
|
||||
<TableHead scope="col">주요 검토내용</TableHead>
|
||||
<TableHead scope="col">사용 여부</TableHead>
|
||||
<TableHead scope="col">체크 사용</TableHead>
|
||||
<TableHead scope="col">정렬 순서</TableHead>
|
||||
<TableHead scope="col">구분</TableHead>
|
||||
<TableHead scope="col">관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -55,7 +55,11 @@ function CklstRvwRowComponent({
|
||||
onValueChange={(v) => onChange({ cklstArtclSn: Number(v), isDirty: true })}
|
||||
disabled={!row.isNew}
|
||||
>
|
||||
<SelectTrigger className="w-full flex-1">
|
||||
{/* 비활성(기존 항목) 상태에서도 텍스트 명도 대비 4.5:1 이상 유지 */}
|
||||
<SelectTrigger
|
||||
title="점검항목 선택"
|
||||
className="w-full flex-1 disabled:opacity-100"
|
||||
>
|
||||
<SelectValue placeholder="점검항목 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -73,12 +77,14 @@ function CklstRvwRowComponent({
|
||||
value={row.rvwCn ?? ""}
|
||||
onChange={(e) => onChange({ rvwCn: e.target.value, isDirty: true })}
|
||||
placeholder="주요 검토내용"
|
||||
title="주요 검토내용"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
<RadioGroup
|
||||
value={row.useYn ?? "Y"}
|
||||
onValueChange={(v) => onChange({ useYn: v as "Y" | "N", isDirty: true })}
|
||||
aria-label="사용 여부"
|
||||
className="flex justify-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -95,6 +101,7 @@ function CklstRvwRowComponent({
|
||||
<RadioGroup
|
||||
value={row.bchckUseYn ?? "Y"}
|
||||
onValueChange={(v) => onChange({ bchckUseYn: v as "Y" | "N", isDirty: true })}
|
||||
aria-label="체크 사용"
|
||||
className="flex justify-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -111,6 +118,7 @@ function CklstRvwRowComponent({
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
title="정렬 순서"
|
||||
value={row.sortSeq ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({
|
||||
|
||||
@@ -103,7 +103,7 @@ function AcntgCodeTabPanel() {
|
||||
</p>
|
||||
) : (
|
||||
<div className="max-h-[60rem] overflow-y-auto px-4 pb-4 pt-1">
|
||||
<Table className="w-full">
|
||||
<Table caption="회계코드 목록으로 코드, 코드명, 설명, 사용 여부, 정렬순서, 구분, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="14%" />
|
||||
<col width="16%" />
|
||||
@@ -115,13 +115,13 @@ function AcntgCodeTabPanel() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>코드</TableHead>
|
||||
<TableHead>코드명</TableHead>
|
||||
<TableHead>설명</TableHead>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
<TableHead scope="col">코드</TableHead>
|
||||
<TableHead scope="col">코드명</TableHead>
|
||||
<TableHead scope="col">설명</TableHead>
|
||||
<TableHead scope="col">사용 여부</TableHead>
|
||||
<TableHead scope="col">정렬순서</TableHead>
|
||||
<TableHead scope="col">구분</TableHead>
|
||||
<TableHead scope="col">관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -40,9 +40,10 @@ function AcntgSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Acn
|
||||
value={row.acntgSeCd}
|
||||
onChange={(e) => onChange({ acntgSeCd: e.target.value })}
|
||||
placeholder="코드"
|
||||
title="코드"
|
||||
maxLength={10}
|
||||
disabled={!row.isNew}
|
||||
className="flex-1"
|
||||
className="flex-1 disabled:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -51,6 +52,7 @@ function AcntgSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Acn
|
||||
value={row.acntgSeCdNm ?? ""}
|
||||
onChange={(e) => onChange({ acntgSeCdNm: e.target.value })}
|
||||
placeholder="코드명"
|
||||
title="코드명"
|
||||
maxLength={100}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -59,6 +61,7 @@ function AcntgSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Acn
|
||||
value={row.expln ?? ""}
|
||||
onChange={(e) => onChange({ expln: e.target.value })}
|
||||
placeholder="설명"
|
||||
title="설명"
|
||||
maxLength={1000}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -66,6 +69,7 @@ function AcntgSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Acn
|
||||
<RadioGroup
|
||||
value={row.useYn === "N" ? "N" : "Y"}
|
||||
onValueChange={(v) => onChange({ useYn: v })}
|
||||
aria-label="사용 여부"
|
||||
className="flex justify-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -82,6 +86,7 @@ function AcntgSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Acn
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
title="정렬순서"
|
||||
value={row.sortSeq ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({ sortSeq: e.target.value ? Number(e.target.value) : undefined })
|
||||
|
||||
@@ -103,7 +103,7 @@ function CnstpCodeTabPanel() {
|
||||
</p>
|
||||
) : (
|
||||
<div className="max-h-[60rem] overflow-y-auto px-4 pb-4 pt-1">
|
||||
<Table className="w-full">
|
||||
<Table caption="공사코드 목록으로 코드, 코드명, 설명, 숫자 여부, 소숫점 단위, 사용 여부, 정렬순서, 구분, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="14%" />
|
||||
<col width="16%" />
|
||||
@@ -115,15 +115,15 @@ function CnstpCodeTabPanel() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>코드</TableHead>
|
||||
<TableHead>코드명</TableHead>
|
||||
<TableHead>설명</TableHead>
|
||||
<TableHead>숫자 여부</TableHead>
|
||||
<TableHead>소숫점 단위</TableHead>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
<TableHead scope="col">코드</TableHead>
|
||||
<TableHead scope="col">코드명</TableHead>
|
||||
<TableHead scope="col">설명</TableHead>
|
||||
<TableHead scope="col">숫자 여부</TableHead>
|
||||
<TableHead scope="col">소숫점 단위</TableHead>
|
||||
<TableHead scope="col">사용 여부</TableHead>
|
||||
<TableHead scope="col">정렬순서</TableHead>
|
||||
<TableHead scope="col">구분</TableHead>
|
||||
<TableHead scope="col">관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -40,9 +40,10 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
value={row.cnstpSeCd}
|
||||
onChange={(e) => onChange({ cnstpSeCd: e.target.value })}
|
||||
placeholder="코드"
|
||||
title="코드"
|
||||
maxLength={10}
|
||||
disabled={!row.isNew}
|
||||
className="flex-1"
|
||||
className="flex-1 disabled:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -51,6 +52,7 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
value={row.cnstpSeCdNm ?? ""}
|
||||
onChange={(e) => onChange({ cnstpSeCdNm: e.target.value })}
|
||||
placeholder="코드명"
|
||||
title="코드명"
|
||||
maxLength={100}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -59,6 +61,7 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
value={row.expln ?? ""}
|
||||
onChange={(e) => onChange({ expln: e.target.value })}
|
||||
placeholder="설명"
|
||||
title="설명"
|
||||
maxLength={1000}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -66,15 +69,16 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
<RadioGroup
|
||||
value={row.numYn === "N" ? "N" : "Y"}
|
||||
onValueChange={(v) => onChange({ numYn: v })}
|
||||
aria-label="숫자 여부"
|
||||
className="flex justify-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="Y" id={`${row.rowKey}-num-y`} />
|
||||
<Label htmlFor={`${row.rowKey}-use-y`}>예</Label>
|
||||
<Label htmlFor={`${row.rowKey}-num-y`}>예</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="N" id={`${row.rowKey}-num-n`} />
|
||||
<Label htmlFor={`${row.rowKey}-use-n`}>아니오</Label>
|
||||
<Label htmlFor={`${row.rowKey}-num-n`}>아니오</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</TableCell>
|
||||
@@ -82,6 +86,7 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
title="소숫점 단위"
|
||||
value={row.dcptUnitCnt ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({ dcptUnitCnt: e.target.value ? Number(e.target.value) : undefined })
|
||||
@@ -93,6 +98,7 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
<RadioGroup
|
||||
value={row.useYn === "N" ? "N" : "Y"}
|
||||
onValueChange={(v) => onChange({ useYn: v })}
|
||||
aria-label="사용 여부"
|
||||
className="flex justify-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -109,6 +115,7 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
title="정렬순서"
|
||||
value={row.sortSeq ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({ sortSeq: e.target.value ? Number(e.target.value) : undefined })
|
||||
|
||||
@@ -103,7 +103,7 @@ function FnrsSharCodeTabPanel() {
|
||||
</p>
|
||||
) : (
|
||||
<div className="max-h-[60rem] overflow-y-auto px-4 pb-4 pt-1">
|
||||
<Table className="w-full">
|
||||
<Table caption="재원분담구분코드 목록으로 코드, 코드명, 설명, 사용 여부, 정렬순서, 구분, 관리 정보 제공" className="w-full">
|
||||
<colgroup>
|
||||
<col width="14%" />
|
||||
<col width="16%" />
|
||||
@@ -115,13 +115,13 @@ function FnrsSharCodeTabPanel() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>코드</TableHead>
|
||||
<TableHead>코드명</TableHead>
|
||||
<TableHead>설명</TableHead>
|
||||
<TableHead>사용 여부</TableHead>
|
||||
<TableHead>정렬순서</TableHead>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead>관리</TableHead>
|
||||
<TableHead scope="col">코드</TableHead>
|
||||
<TableHead scope="col">코드명</TableHead>
|
||||
<TableHead scope="col">설명</TableHead>
|
||||
<TableHead scope="col">사용 여부</TableHead>
|
||||
<TableHead scope="col">정렬순서</TableHead>
|
||||
<TableHead scope="col">구분</TableHead>
|
||||
<TableHead scope="col">관리</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
|
||||
@@ -40,9 +40,10 @@ function FnrsSharSeRowComponent({ row, depth, onChange, onAddChild, onDelete }:
|
||||
value={row.fnrsSharSeCd}
|
||||
onChange={(e) => onChange({ fnrsSharSeCd: e.target.value })}
|
||||
placeholder="코드"
|
||||
title="코드"
|
||||
maxLength={10}
|
||||
disabled={!row.isNew}
|
||||
className="flex-1"
|
||||
className="flex-1 disabled:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -51,6 +52,7 @@ function FnrsSharSeRowComponent({ row, depth, onChange, onAddChild, onDelete }:
|
||||
value={row.fnrsSharSeCdNm ?? ""}
|
||||
onChange={(e) => onChange({ fnrsSharSeCdNm: e.target.value })}
|
||||
placeholder="코드명"
|
||||
title="코드명"
|
||||
maxLength={100}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -59,6 +61,7 @@ function FnrsSharSeRowComponent({ row, depth, onChange, onAddChild, onDelete }:
|
||||
value={row.expln ?? ""}
|
||||
onChange={(e) => onChange({ expln: e.target.value })}
|
||||
placeholder="설명"
|
||||
title="설명"
|
||||
maxLength={1000}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -66,6 +69,7 @@ function FnrsSharSeRowComponent({ row, depth, onChange, onAddChild, onDelete }:
|
||||
<RadioGroup
|
||||
value={row.useYn === "N" ? "N" : "Y"}
|
||||
onValueChange={(v) => onChange({ useYn: v })}
|
||||
aria-label="사용 여부"
|
||||
className="flex justify-center gap-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -82,6 +86,7 @@ function FnrsSharSeRowComponent({ row, depth, onChange, onAddChild, onDelete }:
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
title="정렬순서"
|
||||
value={row.sortSeq ?? ""}
|
||||
onChange={(e) =>
|
||||
onChange({ sortSeq: e.target.value ? Number(e.target.value) : undefined })
|
||||
|
||||
@@ -56,14 +56,12 @@
|
||||
fill: rgba(0, 150, 255, 0.2) !important;
|
||||
stroke: #0066cc;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(.cls-1) {
|
||||
fill: rgba(0, 150, 255, 0.2) !important;
|
||||
stroke: #0066cc;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.svgContainer :global(svg) :global(.basin) {
|
||||
@@ -93,7 +91,6 @@
|
||||
fill: rgba(0, 209, 209, 0.5);
|
||||
stroke: #399ac3;
|
||||
stroke-width: 1.3;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.popup {
|
||||
@@ -155,7 +152,7 @@
|
||||
}
|
||||
|
||||
.basinLabel.active {
|
||||
background: #006bf8;
|
||||
background: #0056b3;
|
||||
color: #fff;
|
||||
border-color: #96c3ff;
|
||||
box-shadow: 0 4px 12px rgba(0, 107, 248, 0.3);
|
||||
|
||||
@@ -247,6 +247,11 @@ export function useNationalAsMap(basinDocPrgrsList: BasinDocPrgrsResponseDto[] |
|
||||
svg = svgLayerRef.current.querySelector("svg");
|
||||
if (!svg) throw new Error("SVG element not found in loaded content");
|
||||
|
||||
// 확대·축소해도 선 두께 유지. CSS 의 vector-effect 는 W3C CSS 검사 오류로 처리되므로 SVG 속성으로 지정
|
||||
svg.querySelectorAll("path, polygon, polyline").forEach((shape) => {
|
||||
shape.setAttribute("vector-effect", "non-scaling-stroke");
|
||||
});
|
||||
|
||||
applyViewBox(svg, stageRef.current);
|
||||
groups = collectBasinGroups(svg);
|
||||
labels = labelsLayerRef.current ? createLabels(labelsLayerRef.current, groups) : [];
|
||||
|
||||
@@ -40,18 +40,18 @@ export function DashboardNotiList({
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="card card-border m-0 bg-background">
|
||||
<section className="card card-border m-0 bg-[#FAFBFC]">
|
||||
<div className="card-header flex items-center justify-between gap-4">
|
||||
<h3 className="text-2xl font-semibold">
|
||||
알림이력
|
||||
{dataUpdatedAt > 0 ? (
|
||||
<span className="pl-2 text-lg font-medium text-muted-foreground">
|
||||
<span className="pl-2 text-lg font-medium text-[#4A4A4A]">
|
||||
(업데이트 일시: {dayjs(dataUpdatedAt).format("YYYY-MM-DD HH:mm")})
|
||||
</span>
|
||||
) : null}
|
||||
</h3>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button type="button" size="sm" color="white" onClick={() => router.push("/nti-hstry")}>
|
||||
<Button type="button" size="sm" color="white" className="!text-[#0056B3]" onClick={() => router.push("/nti-hstry")}>
|
||||
전체보기
|
||||
</Button>
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@ export function DashboardNotiList({
|
||||
<div className="flex flex-col gap-4 p-2">
|
||||
<div className="flex flex-wrap gap-2 pt-2">
|
||||
{NOTI_STAT_CHIPS.map((chip) => (
|
||||
<Badge key={chip.field} color={chip.color}>
|
||||
<Badge key={chip.field} color={chip.color} className={chip.className}>
|
||||
{chip.label} {isPending ? "-" : (notiStats?.[chip.field] ?? 0)}건
|
||||
</Badge>
|
||||
))}
|
||||
@@ -75,7 +75,7 @@ export function DashboardNotiList({
|
||||
) : null}
|
||||
|
||||
{isError ? (
|
||||
<p className="flex min-h-[12rem] items-center justify-center text-muted-foreground">
|
||||
<p className="flex min-h-[12rem] items-center justify-center text-[#4A4A4A]">
|
||||
알림이력을 불러오지 못했습니다.
|
||||
</p>
|
||||
) : null}
|
||||
@@ -110,11 +110,11 @@ interface DashboardNotiRowProps {
|
||||
function DashboardNotiRow({ row, isActionPending, onAction }: DashboardNotiRowProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-4 border-b px-2 py-4 last:border-b-0">
|
||||
<span className="text-lg text-muted-foreground">
|
||||
<span className="text-lg text-[#4A4A4A]">
|
||||
{row.regDt ? dayjs(row.regDt).format("YYYY-MM-DD") : "-"}
|
||||
</span>
|
||||
<p className="min-w-0 line-clamp-2 text-xl sm:line-clamp-1">{row.notiTtl}</p>
|
||||
<Button type="button" size="sm" color="white" disabled={isActionPending} onClick={onAction}>
|
||||
<Button type="button" size="sm" color="white" className="!text-[#0056B3]" disabled={isActionPending} onClick={onAction}>
|
||||
처리하기
|
||||
</Button>
|
||||
</div>
|
||||
@@ -137,7 +137,7 @@ function resolveNotiUrl(row: NotiAsUserResponseDto) {
|
||||
}
|
||||
|
||||
const NOTI_STAT_CHIPS = [
|
||||
{ field: "todayCount", label: "오늘", color: "green" },
|
||||
{ field: "delayCount", label: "지연", color: "orange" },
|
||||
{ field: "pendingCount", label: "완료대기", color: "gray" },
|
||||
{ field: "todayCount", label: "오늘", color: "green", className: "!bg-[#188038]" },
|
||||
{ field: "delayCount", label: "지연", color: "orange", className: "!bg-[#C5221F]" },
|
||||
{ field: "pendingCount", label: "완료대기", color: "gray", className: "!bg-[#5F6368]" },
|
||||
] as const;
|
||||
|
||||
@@ -39,16 +39,17 @@ export function DashboardNoticeTable() {
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "공지사항 목록으로 번호, 제목, 작성자, 작성일, 조회수 정보 제공",
|
||||
variant: "line",
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="card card-border m-0 bg-background">
|
||||
<section className="card card-border m-0 bg-[#FAFBFC]">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">공지사항</h3>
|
||||
<Button type="button" size="sm" color="white" onClick={handleMoreClick}>
|
||||
<Button type="button" size="sm" color="white" className="!text-[#0056B3]" onClick={handleMoreClick}>
|
||||
더보기
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export function DashboardPage() {
|
||||
const { data, isPending, isError, dataUpdatedAt } = useGetMainDashboard();
|
||||
|
||||
return (
|
||||
<div className="mt-4 flex gap-4 px-[1.8rem] pb-6">
|
||||
<div className="flex min-h-full gap-4 bg-[#E9EDF2] px-[1.8rem] pb-6 pt-4 text-[#1E1E1E]">
|
||||
<div className="hidden flex-[0.5] sm:block">
|
||||
<NationalAsMap basinDocPrgrsList={data?.basinDocPrgrsList} />
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function DocPrgrsSttsCards({ items, isPending, isError }: DocPrgrsSttsCar
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<div className="card card-border m-0 flex min-h-[14rem] items-center justify-center bg-background text-muted-foreground">
|
||||
<div className="card card-border m-0 flex min-h-[14rem] items-center justify-center bg-[#FAFBFC] text-[#4A4A4A]">
|
||||
진행상황을 불러오지 못했습니다.
|
||||
</div>
|
||||
);
|
||||
@@ -39,7 +39,7 @@ export function DocPrgrsSttsCards({ items, isPending, isError }: DocPrgrsSttsCar
|
||||
return (
|
||||
<div
|
||||
key={slot.docSeComCd}
|
||||
className="card card-border hover:bg-muted/40 m-0 block bg-background transition-colors"
|
||||
className="card card-border hover:bg-muted/40 m-0 block bg-[#FAFBFC] transition-colors"
|
||||
>
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">{slot.title}</h3>
|
||||
@@ -90,7 +90,7 @@ function DocPrgrsSttsMetric({
|
||||
>
|
||||
<Icon className="size-6" />
|
||||
</span>
|
||||
<span className="text-xl text-muted-foreground">{label}</span>
|
||||
<span className="text-xl text-[#4A4A4A]">{label}</span>
|
||||
<span className="text-3xl font-semibold tabular-nums">{count}</span>
|
||||
</div>
|
||||
);
|
||||
@@ -107,21 +107,21 @@ const STAT_METRICS = [
|
||||
{
|
||||
field: "reqCnt",
|
||||
label: "신청",
|
||||
iconClassName: "bg-blue-500",
|
||||
iconClassName: "bg-[#4285F4]",
|
||||
bgColor: "bg-blue-100",
|
||||
Icon: FilePlus,
|
||||
},
|
||||
{
|
||||
field: "prgrsCnt",
|
||||
label: "진행",
|
||||
iconClassName: "bg-emerald-500",
|
||||
iconClassName: "bg-[#34A853]",
|
||||
bgColor: "bg-emerald-100",
|
||||
Icon: RefreshCw,
|
||||
},
|
||||
{
|
||||
field: "cmptnCnt",
|
||||
label: "완료",
|
||||
iconClassName: "bg-orange-500",
|
||||
iconClassName: "bg-[#EA4335]",
|
||||
bgColor: "bg-orange-100",
|
||||
Icon: CircleCheck,
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ export function NationalAsMap({ basinDocPrgrsList }: NationalAsMapProps) {
|
||||
const popupColumnSpan = Math.max(1, popupContent.items.length);
|
||||
|
||||
return (
|
||||
<section className="card card-border m-0 flex h-full min-h-[40rem] flex-col bg-background">
|
||||
<section className="card card-border m-0 flex h-full min-h-[40rem] flex-col bg-[#FAFBFC]">
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-semibold">전국 행정지원 지도</h3>
|
||||
</div>
|
||||
|
||||
@@ -79,6 +79,17 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
@@ -95,7 +106,9 @@ function ProgressStepBadge({ prgrsSttsComCd }: ProgressStepBadgeProps) {
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={`${badge.label}-${index}`}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>{badge.label}</Badge>
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -165,8 +165,11 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="mt-6 space-y-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<div className="sticky top-[6rem] z-30 flex items-center justify-between bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="text-red-600">*</span>은 필수입력 항목입니다.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{initialData?.detDto?.designCnstnDocNo && (
|
||||
|
||||
@@ -59,6 +59,7 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "설계자문 목록으로 시도, 시군구, 사업명, 업무구분, 담당자, 해당기관, 진행상황, 신청일 정보 제공",
|
||||
viewPagination: true,
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -45,7 +45,7 @@ function BasicInfoFormSection() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" data-required>
|
||||
<TableHead scope="row" className="w-[20rem]" data-required>
|
||||
사업명
|
||||
</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
@@ -73,7 +73,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업기간</TableHead>
|
||||
<TableHead scope="row" data-required>사업기간</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<FieldGroup className="flex-row items-center gap-2">
|
||||
<Controller
|
||||
@@ -117,7 +117,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업내용</TableHead>
|
||||
<TableHead scope="row" data-required>사업내용</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="designCnstnMng.bizCn"
|
||||
@@ -137,9 +137,9 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>구분</TableHead>
|
||||
<TableHead scope="row" data-required>구분</TableHead>
|
||||
<TableCell>{taskSeComCdLabel}</TableCell>
|
||||
<TableHead>신청 지자체</TableHead>
|
||||
<TableHead scope="row">신청 지자체</TableHead>
|
||||
<TableCell>{localGovLabel}</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -11,7 +11,7 @@ interface BasicInfoSectionProps {
|
||||
function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본 정보로 사업명, 사업기간, 사업내용, 환경청구분, 구분, 신청지자체 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
|
||||
@@ -13,7 +13,7 @@ function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="사업규모">
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="사업규모 목록으로 구분, 기본계획(A), 예산반영(B), 금회설계시(C), 증감(C-B) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]">구분</TableHead>
|
||||
@@ -36,7 +36,7 @@ function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) {
|
||||
|
||||
return (
|
||||
<TableRow key={node.cnstpSeCd}>
|
||||
<TableHead className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
{node.cnstpSeCdNm}
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
|
||||
@@ -11,21 +11,21 @@ interface ContactInfoSectionProps {
|
||||
function ContactInfoSection({ det }: ContactInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="담당자 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="담당자 정보로 신청자(이름, 연락처, 소속, 이메일)과 설계자(이름, 연락처, 소속, 이메일) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
{det.aplcntNm && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>신청자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>신청자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{det.aplcntNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{det.aplcntMblTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{det.aplcntOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{det.aplcntEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
@@ -33,16 +33,16 @@ function ContactInfoSection({ det }: ContactInfoSectionProps) {
|
||||
{det.dsgnrNm && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>설계자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>설계자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{det.dsgnrNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{det.dsgnrTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{det.dsgnrOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{det.dsgnrEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
@@ -180,10 +180,10 @@ const TechReviewOpinionSection = forwardRef<
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
<TableHead scope="row" className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
@@ -197,7 +197,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead scope="row">검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
@@ -234,7 +234,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead scope="row">첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -289,7 +289,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
|
||||
@@ -100,6 +100,7 @@ function EsntlAtchFileList({
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "필수첨부파일 목록으로 필수첨부파일아이디, 문서구분, 필수첨부파일명, 필수첨부여부, 비고, 양식 내려받기, 관리 정보 제공",
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -120,6 +120,17 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
@@ -137,7 +148,9 @@ function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadg
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={badge.label}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>{badge.label}</Badge>
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -53,21 +53,52 @@ export function createFileVrfcSplmntColumns() {
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("fileSeComCd", {
|
||||
header: () => <p className="whitespace-nowrap">파일유형</p>,
|
||||
meta: { align: "left" },
|
||||
header: "파일유형",
|
||||
// 고정 너비 + 자동 줄바꿈 (긴 텍스트로 표가 가로로 늘어나지 않도록)
|
||||
meta: { align: "left", width: 100 },
|
||||
cell: ({ getValue }) => (
|
||||
<p>{formatOptional(FILE_SE_COM_CD_MAP[getValue() as keyof typeof FILE_SE_COM_CD_MAP])}</p>
|
||||
<p className="whitespace-normal break-words">
|
||||
{formatOptional(FILE_SE_COM_CD_MAP[getValue() as keyof typeof FILE_SE_COM_CD_MAP])}
|
||||
</p>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("fileSeNm", {
|
||||
header: () => <p className="whitespace-nowrap">파일구분</p>,
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => <p className="whitespace-nowrap">{formatOptional(getValue())}</p>,
|
||||
header: "파일구분",
|
||||
meta: { align: "left", width: 320 },
|
||||
// 표 레이아웃과 무관하게 셀 내용 너비를 320px 로 고정하고 넘치는 텍스트는 자동 줄바꿈
|
||||
cell: ({ getValue }) => (
|
||||
<p
|
||||
style={{
|
||||
width: 320,
|
||||
maxWidth: 320,
|
||||
// 검증내역과 글자가 붙어 보이지 않도록 오른쪽 여백
|
||||
paddingRight: 12,
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "normal",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
>
|
||||
{formatOptional(getValue())}
|
||||
</p>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("vrfcRsltCn", {
|
||||
header: "검증내역",
|
||||
// 너비 미지정: 나머지 영역을 사용, 넘치는 텍스트는 자동 줄바꿈
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
cell: ({ getValue }) => (
|
||||
<p
|
||||
style={{
|
||||
// 파일구분과 글자가 붙어 보이지 않도록 왼쪽 여백
|
||||
paddingLeft: 12,
|
||||
whiteSpace: "normal",
|
||||
wordBreak: "normal",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
>
|
||||
{formatOptional(getValue())}
|
||||
</p>
|
||||
),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ function ChecklistTaskPanel({
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption={`체크리스트 목록으로 검토항목, 주요 검토내용, 검토 결과${selectedReviewStep !== "LOCAL" ? ", 기술검토 의견" : ""} 정보 제공`} variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" rowSpan={2}>
|
||||
|
||||
@@ -115,6 +115,9 @@ function FileVrfcSplmntDialog({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "파일보완 목록으로 선택, 파일유형, 파일구분, 검증내역 정보 제공",
|
||||
// 열 너비를 고정해 긴 텍스트는 셀 안에서 줄바꿈
|
||||
className: "table-fixed",
|
||||
viewPagination: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ function PwrkCnsltnDocPjtcoAjmtTable({
|
||||
}
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<Table caption="금액검증결과 목록으로 구분, 검증결과, 검증금액 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="30%" />
|
||||
<col width="50%" />
|
||||
|
||||
@@ -157,7 +157,7 @@ function CnsltnListTable({
|
||||
const isSomeSelected = selectedCount > 0 && !isAllSelected;
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토 결과 조회 목록으로 선택, 구분, 검토의견 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="4%" />
|
||||
<col width="4%" />
|
||||
|
||||
@@ -248,8 +248,11 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<div className="sticky top-[6rem] z-30 flex items-center justify-between bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="text-red-600">*</span>은 필수입력 항목입니다.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{initialData?.detDto?.fnrsCnsltnDocNo && (
|
||||
|
||||
@@ -59,6 +59,7 @@ function FnrsList({ searchParams, pagination, onPaginationChange }: FnrsListProp
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "재원협의 목록으로 시도, 시군구, 사업명, 업무구분, 담당자, 해당기관, 진행상황, 신청일 정보 제공",
|
||||
viewPagination: true,
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체, 시설종류, 재이용용도 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -58,7 +58,7 @@ function BasicInfoFormSection() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" data-required>
|
||||
<TableHead scope="row" className="w-[20rem]" data-required>
|
||||
사업명
|
||||
</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
@@ -86,7 +86,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업기간</TableHead>
|
||||
<TableHead scope="row" data-required>사업기간</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<FieldGroup className="flex-row items-center gap-2">
|
||||
<Controller
|
||||
@@ -130,7 +130,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업내용</TableHead>
|
||||
<TableHead scope="row" data-required>사업내용</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="fnrsCnsltnMng.bizCn"
|
||||
@@ -150,15 +150,15 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>구분</TableHead>
|
||||
<TableHead scope="row" data-required>구분</TableHead>
|
||||
<TableCell>{taskSeComCdLabel}</TableCell>
|
||||
<TableHead>신청 지자체</TableHead>
|
||||
<TableHead scope="row">신청 지자체</TableHead>
|
||||
<TableCell>{localGovLabel}</TableCell>
|
||||
</TableRow>
|
||||
{taskSeComCd.includes("RE") && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>시설종류</TableHead>
|
||||
<TableHead scope="row">시설종류</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="fnrsCnsltnMng.rtlztnFcltSeComCd"
|
||||
@@ -198,7 +198,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>재이용용도</TableHead>
|
||||
<TableHead scope="row">재이용용도</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="fnrsCnsltnMng.rtlztnUsgSeComCd"
|
||||
|
||||
@@ -11,7 +11,7 @@ interface BasicInfoSectionProps {
|
||||
function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본 정보로 사업명, 사업기간, 사업내용, 환경청구분, 구분, 신청지자체, 확정예산사업번호, 예산신청연도 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -20,31 +20,31 @@ function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>사업명</TableHead>
|
||||
<TableHead scope="row">사업명</TableHead>
|
||||
<TableCell>{det.bizNm || "-"}</TableCell>
|
||||
<TableHead>사업기간</TableHead>
|
||||
<TableHead scope="row">사업기간</TableHead>
|
||||
<TableCell>{(det.bizBgngYr || "-") + " ~ " + (det.bizEndYr || "-")}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사업내용</TableHead>
|
||||
<TableHead scope="row">사업내용</TableHead>
|
||||
<TableCell colSpan={3}>{det.bizCn || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>환경청구분</TableHead>
|
||||
<TableHead scope="row">환경청구분</TableHead>
|
||||
<TableCell>{det.envoSeCdNm || "-"}</TableCell>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead scope="row">구분</TableHead>
|
||||
<TableCell>{det.taskSeCdNm || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>신청지자체</TableHead>
|
||||
<TableHead scope="row">신청지자체</TableHead>
|
||||
<TableCell>
|
||||
{det.sido || "-"} {det.gugun || "-"}
|
||||
</TableCell>
|
||||
<TableHead>확정예산사업번호</TableHead>
|
||||
<TableHead scope="row">확정예산사업번호</TableHead>
|
||||
<TableCell>{det.cfmtnBgtBizNo || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>예산신청연도</TableHead>
|
||||
<TableHead scope="row">예산신청연도</TableHead>
|
||||
<TableCell colSpan={3}>{det.bgtAplyYr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -20,7 +20,7 @@ function BusinessCostSection({ bizCstList }: BusinessCostSectionProps) {
|
||||
<SectionCollapsible title="총사업비">
|
||||
<div className="overflow-x-auto">
|
||||
<p className="mb-2 text-right font-normal text-muted-foreground">(단위: 백만원)</p>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="총사업비 목록으로 구분, 예산반영(A), 표준사업비(B), 금회설계시(C), 증감 정보 제공" variant="secondary" className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]" rowSpan={2}>
|
||||
@@ -51,9 +51,9 @@ function BusinessCostSection({ bizCstList }: BusinessCostSectionProps) {
|
||||
return (
|
||||
<TableRow key={node.acntgSeCd}>
|
||||
{node.treeLevel <= 1 ? (
|
||||
<TableHead className="!px-[0.8rem] text-left">{node.acntgSeCdNm}</TableHead>
|
||||
<TableHead scope="row" className="!px-[0.8rem] text-left">{node.acntgSeCdNm}</TableHead>
|
||||
) : (
|
||||
<TableHead className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
{showSubIcon && <span className="mr-1 text-gray-400">└</span>}
|
||||
{node.acntgSeCdNm}
|
||||
</TableHead>
|
||||
|
||||
@@ -13,7 +13,7 @@ function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="사업규모">
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="사업규모 목록으로 구분, 기본계획(A), 예산반영(B), 금회설계시(C), 증감(C-B) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]">구분</TableHead>
|
||||
@@ -36,7 +36,7 @@ function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) {
|
||||
|
||||
return (
|
||||
<TableRow key={node.cnstpSeCd}>
|
||||
<TableHead className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
{node.cnstpSeCdNm}
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
|
||||
@@ -86,7 +86,13 @@ function ChecklistSection({
|
||||
>
|
||||
<TabsList>
|
||||
{visibleReviewSteps.map((stepCode) => (
|
||||
<TabsTrigger key={stepCode} value={stepCode} className="!px-6 !py-3 text-xl">
|
||||
<TabsTrigger
|
||||
key={stepCode}
|
||||
value={stepCode}
|
||||
id={`fnrs-cklst-step-tab-${stepCode}`}
|
||||
aria-controls="fnrs-cklst-task-tabs"
|
||||
className="!px-6 !py-3 text-xl"
|
||||
>
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
@@ -96,6 +102,7 @@ function ChecklistSection({
|
||||
|
||||
{taskCodes.length > 0 ? (
|
||||
<Tabs
|
||||
id="fnrs-cklst-task-tabs"
|
||||
value={selectedTaskCode ?? taskCodes[0]}
|
||||
onValueChange={setSelectedTaskCode}
|
||||
className="w-full"
|
||||
@@ -107,6 +114,8 @@ function ChecklistSection({
|
||||
<TabsTrigger
|
||||
key={code}
|
||||
value={code}
|
||||
id={`fnrs-cklst-tab-${code}`}
|
||||
aria-controls={`fnrs-cklst-panel-${code}`}
|
||||
className="!px-6 !py-3 text-xl [&:after]:hidden"
|
||||
>
|
||||
{TASK_CODE_LABELS[code] ?? code}
|
||||
@@ -120,7 +129,15 @@ function ChecklistSection({
|
||||
</div>
|
||||
|
||||
{taskCodes.map((code) => (
|
||||
<TabsContent key={code} value={code} className="mt-0 border-0 p-0 shadow-none ring-0">
|
||||
<TabsContent
|
||||
key={code}
|
||||
value={code}
|
||||
id={`fnrs-cklst-panel-${code}`}
|
||||
aria-labelledby={`fnrs-cklst-tab-${code}`}
|
||||
className="mt-0 border-0 p-0 shadow-none ring-0"
|
||||
>
|
||||
{/* 선택된 탭(검토단계·업무구분)의 패널임을 알 수 있도록 숨김 제목 제공 */}
|
||||
<h3 className="sr-only">{CHECKLIST_TAB_LABELS[selectedReviewStep]} {TASK_CODE_LABELS[code] ?? code} 체크리스트</h3>
|
||||
<ChecklistTaskPanel
|
||||
selectedReviewStep={selectedReviewStep}
|
||||
selectedRows={getChecklistDisplayRows(cklstResultList, code)}
|
||||
|
||||
@@ -11,21 +11,21 @@ interface ContactInfoSectionProps {
|
||||
function ContactInfoSection({ det }: ContactInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="담당자 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="담당자 정보로 신청자(이름, 연락처, 소속, 이메일)과 설계자(이름, 연락처, 소속, 이메일) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
{det.aplcntNm && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>신청자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>신청자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{det.aplcntNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{det.aplcntMblTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{det.aplcntOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{det.aplcntEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
@@ -33,16 +33,16 @@ function ContactInfoSection({ det }: ContactInfoSectionProps) {
|
||||
{det.dsgnrNm && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>설계자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>설계자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{det.dsgnrNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{det.dsgnrTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{det.dsgnrOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{det.dsgnrEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
@@ -21,7 +21,7 @@ function FnrsSharSection({ fnrsSharList }: FnrsSharSectionProps) {
|
||||
<SectionCollapsible title="재원분담">
|
||||
<div className="overflow-x-auto">
|
||||
<p className="mb-2 text-right font-normal text-muted-foreground">(단위: 백만원)</p>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="재원분담 목록으로 구분, 현행(A), 변경요구(B), 증감, 비고 정보 제공" variant="secondary" className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[24rem]" rowSpan={2}>
|
||||
@@ -51,7 +51,7 @@ function FnrsSharSection({ fnrsSharList }: FnrsSharSectionProps) {
|
||||
|
||||
return (
|
||||
<TableRow key={node.fnrsSharSeCd}>
|
||||
<TableHead className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
{showSubIcon && <span className="mr-1 text-gray-400">└</span>}
|
||||
{node.fnrsSharSeCdNm}
|
||||
</TableHead>
|
||||
|
||||
@@ -189,7 +189,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
@@ -303,7 +303,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
|
||||
@@ -121,6 +121,17 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
@@ -138,7 +149,9 @@ function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadg
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={badge.label}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>{badge.label}</Badge>
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ function ChecklistTaskPanel({
|
||||
return (
|
||||
<>
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption={`체크리스트 목록으로 검토항목, 주요 검토내용, 검토 결과${selectedReviewStep !== "LOCAL" ? ", 기술검토 의견" : ""} 정보 제공`} variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" rowSpan={2}>
|
||||
|
||||
@@ -209,8 +209,11 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<div className="sticky top-[6rem] z-30 flex items-center justify-between bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="text-red-600">*</span>은 필수입력 항목입니다.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{initialData?.detDto.instlSncnDocNo && (
|
||||
|
||||
@@ -59,6 +59,7 @@ function InstlList({ searchParams, pagination, onPaginationChange }: InstlListPr
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "설치인가 목록으로 시도, 시군구, 사업명, 업무구분, 담당자, 해당기관, 진행상황, 신청일 정보 제공",
|
||||
viewPagination: true,
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
|
||||
@@ -53,7 +53,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체, 사업구분, 시설종류, 재이용용도 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -62,7 +62,7 @@ function BasicInfoFormSection() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" data-required>
|
||||
<TableHead scope="row" className="w-[20rem]" data-required>
|
||||
사업명
|
||||
</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
@@ -90,7 +90,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업기간</TableHead>
|
||||
<TableHead scope="row" data-required>사업기간</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<FieldGroup className="flex-row items-center gap-2">
|
||||
<Controller
|
||||
@@ -134,7 +134,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업내용</TableHead>
|
||||
<TableHead scope="row" data-required>사업내용</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="instlSncnMng.bizCn"
|
||||
@@ -154,13 +154,13 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>구분</TableHead>
|
||||
<TableHead scope="row" data-required>구분</TableHead>
|
||||
<TableCell>{taskSeComCdLabel}</TableCell>
|
||||
<TableHead>신청 지자체</TableHead>
|
||||
<TableHead scope="row">신청 지자체</TableHead>
|
||||
<TableCell>{localGovLabel}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사업구분</TableHead>
|
||||
<TableHead scope="row" data-required>사업구분</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="instlSncnMng.sncnSeComCd"
|
||||
@@ -202,7 +202,7 @@ function BasicInfoFormSection() {
|
||||
{taskSeComCd.includes("RE") && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>시설종류</TableHead>
|
||||
<TableHead scope="row">시설종류</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="instlSncnMng.rtlztnFcltSeComCd"
|
||||
@@ -242,7 +242,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>재이용용도</TableHead>
|
||||
<TableHead scope="row">재이용용도</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="instlSncnMng.rtlztnUsgSeComCd"
|
||||
|
||||
@@ -11,7 +11,7 @@ interface BasicInfoSectionProps {
|
||||
function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="기본 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본 정보로 사업명, 사업기간, 사업내용, 구분, 신청지자체, 사업구분 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -20,25 +20,25 @@ function BasicInfoSection({ det }: BasicInfoSectionProps) {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>사업명</TableHead>
|
||||
<TableHead scope="row">사업명</TableHead>
|
||||
<TableCell>{det.bizNm || "-"}</TableCell>
|
||||
<TableHead>사업기간</TableHead>
|
||||
<TableHead scope="row">사업기간</TableHead>
|
||||
<TableCell>{(det.bizBgngYr || "-") + " ~ " + (det.bizEndYr || "-")}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사업내용</TableHead>
|
||||
<TableHead scope="row">사업내용</TableHead>
|
||||
<TableCell colSpan={3}>{det.bizCn || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead scope="row">구분</TableHead>
|
||||
<TableCell>{det.taskSeCdNm || "-"}</TableCell>
|
||||
<TableHead>신청지자체</TableHead>
|
||||
<TableHead scope="row">신청지자체</TableHead>
|
||||
<TableCell>
|
||||
{det.sido || "-"} {det.gugun || "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>사업구분</TableHead>
|
||||
<TableHead scope="row">사업구분</TableHead>
|
||||
<TableCell colSpan={3}>{det.sncnSeCdNm || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -20,7 +20,7 @@ function BusinessCostSection({ bizCstList }: BusinessCostSectionProps) {
|
||||
<SectionCollapsible title="총사업비">
|
||||
<div className="overflow-x-auto">
|
||||
<p className="mb-2 text-right font-normal text-muted-foreground">(단위: 백만원)</p>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="총사업비 목록으로 구분, 예산반영(A), 표준사업비(B), 금회설계시(C), 증감 정보 제공" variant="secondary" className="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]" rowSpan={2}>
|
||||
@@ -51,9 +51,9 @@ function BusinessCostSection({ bizCstList }: BusinessCostSectionProps) {
|
||||
return (
|
||||
<TableRow key={node.acntgSeCd}>
|
||||
{node.treeLevel <= 1 ? (
|
||||
<TableHead className="!px-[0.8rem] text-left">{node.acntgSeCdNm}</TableHead>
|
||||
<TableHead scope="row" className="!px-[0.8rem] text-left">{node.acntgSeCdNm}</TableHead>
|
||||
) : (
|
||||
<TableHead className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
{showSubIcon && <span className="mr-1 text-gray-400">└</span>}
|
||||
{node.acntgSeCdNm}
|
||||
</TableHead>
|
||||
|
||||
@@ -13,7 +13,7 @@ function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="사업규모">
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="사업규모 목록으로 구분, 기본계획(A), 예산반영(B), 금회설계시(C), 증감(C-B) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]">구분</TableHead>
|
||||
@@ -36,7 +36,7 @@ function BusinessScaleSection({ bizSclList }: BusinessScaleSectionProps) {
|
||||
|
||||
return (
|
||||
<TableRow key={node.cnstpSeCd}>
|
||||
<TableHead className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft: `${pl + 8}px` }}>
|
||||
{node.cnstpSeCdNm}
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
|
||||
@@ -85,7 +85,13 @@ function ChecklistSection({
|
||||
>
|
||||
<TabsList>
|
||||
{visibleReviewSteps.map((stepCode) => (
|
||||
<TabsTrigger key={stepCode} value={stepCode} className="!px-6 !py-3 text-xl">
|
||||
<TabsTrigger
|
||||
key={stepCode}
|
||||
value={stepCode}
|
||||
id={`instl-cklst-step-tab-${stepCode}`}
|
||||
aria-controls="instl-cklst-task-tabs"
|
||||
className="!px-6 !py-3 text-xl"
|
||||
>
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
@@ -95,6 +101,7 @@ function ChecklistSection({
|
||||
|
||||
{taskCodes.length > 0 ? (
|
||||
<Tabs
|
||||
id="instl-cklst-task-tabs"
|
||||
value={selectedTaskCode ?? taskCodes[0]}
|
||||
onValueChange={setSelectedTaskCode}
|
||||
className="w-full"
|
||||
@@ -106,6 +113,8 @@ function ChecklistSection({
|
||||
<TabsTrigger
|
||||
key={code}
|
||||
value={code}
|
||||
id={`instl-cklst-tab-${code}`}
|
||||
aria-controls={`instl-cklst-panel-${code}`}
|
||||
className="!px-6 !py-3 text-xl [&:after]:hidden"
|
||||
>
|
||||
{TASK_CODE_LABELS[code] ?? code}
|
||||
@@ -119,7 +128,15 @@ function ChecklistSection({
|
||||
</div>
|
||||
|
||||
{taskCodes.map((code) => (
|
||||
<TabsContent key={code} value={code} className="mt-0 border-0 p-0 shadow-none ring-0">
|
||||
<TabsContent
|
||||
key={code}
|
||||
value={code}
|
||||
id={`instl-cklst-panel-${code}`}
|
||||
aria-labelledby={`instl-cklst-tab-${code}`}
|
||||
className="mt-0 border-0 p-0 shadow-none ring-0"
|
||||
>
|
||||
{/* 선택된 탭(검토단계·업무구분)의 패널임을 알 수 있도록 숨김 제목 제공 */}
|
||||
<h3 className="sr-only">{CHECKLIST_TAB_LABELS[selectedReviewStep]} {TASK_CODE_LABELS[code] ?? code} 체크리스트</h3>
|
||||
<ChecklistTaskPanel
|
||||
selectedReviewStep={selectedReviewStep}
|
||||
selectedRows={getChecklistDisplayRows(cklstResultList, code)}
|
||||
|
||||
@@ -11,21 +11,21 @@ interface ContactInfoSectionProps {
|
||||
function ContactInfoSection({ det }: ContactInfoSectionProps) {
|
||||
return (
|
||||
<SectionCollapsible title="담당자 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="담당자 정보로 신청자(이름, 연락처, 소속, 이메일)과 설계자(이름, 연락처, 소속, 이메일) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
{det.aplcntNm && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>신청자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>신청자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{det.aplcntNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{det.aplcntMblTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{det.aplcntOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{det.aplcntEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
@@ -33,16 +33,16 @@ function ContactInfoSection({ det }: ContactInfoSectionProps) {
|
||||
{det.dsgnrNm && (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>설계자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>설계자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{det.dsgnrNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{det.dsgnrTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{det.dsgnrOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{det.dsgnrEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
@@ -180,10 +180,10 @@ const TechReviewOpinionSection = forwardRef<
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
<TableHead scope="row" className="w-[12rem]">의견내용</TableHead>
|
||||
<TableCell>
|
||||
<Textarea
|
||||
value={formData.rvwRsltCn}
|
||||
@@ -197,7 +197,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>검토결과</TableHead>
|
||||
<TableHead scope="row">검토결과</TableHead>
|
||||
<TableCell>
|
||||
<FieldSet>
|
||||
<RadioGroup
|
||||
@@ -234,7 +234,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>첨부파일</TableHead>
|
||||
<TableHead scope="row">첨부파일</TableHead>
|
||||
<TableCell>
|
||||
{uploadedFile ? (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -301,7 +301,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
|
||||
@@ -70,6 +70,7 @@ function MyPbadmsSprtList({ searchParams, pagination, onPaginationChange }: MyPb
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "내 신청 목록으로 No, 구분, 신청일, 진행상태, 완료(예정)일, 담당자, 담당자연락처 정보 제공",
|
||||
viewPagination: true,
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
|
||||
@@ -97,6 +97,7 @@ function NtiHstryList({ searchParams, pagination, onPaginationChange }: NtiHstry
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "알림 이력 목록으로 No, 알림일시, 알림내용, 연계업무, 상태, 처리 정보 제공",
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ function InPrgrsDocRcvrList({ selectedRcvrKeys, onRcvrSelectionChange }: InPrgrs
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
...rowSelectionOptions,
|
||||
},
|
||||
dataTableOptions: { caption: "현재 진행중 문서 담당자(신청자) 목록으로 선택, 문서번호, 문서명, 신청자, 상태 정보 제공" },
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -34,6 +34,7 @@ function RvwMnpwRcvrList({ selectedRcvrKeys, onRcvrSelectionChange }: RvwMnpwRcv
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
...rowSelectionOptions,
|
||||
},
|
||||
dataTableOptions: { caption: "검토인력 목록으로 선택, 이름, 소속, 전화번호 정보 제공" },
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,7 +35,7 @@ function SndngList({
|
||||
|
||||
return (
|
||||
<div className="max-h-[40rem] overflow-auto rounded-md border">
|
||||
<Table variant="line" className="text-sm">
|
||||
<Table caption={`발송 목록으로 ${["이름", "전화번호", ...variableNames.map((variableName) => `#${variableName}`), "버튼"].join(", ")} 정보 제공`} variant="line" className="text-sm">
|
||||
<TableHeader className="top-0">
|
||||
<TableRow>
|
||||
<TableHead className="min-w-[100px]">이름</TableHead>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"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";
|
||||
@@ -10,6 +11,14 @@ 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[];
|
||||
@@ -33,8 +42,7 @@ function RvwMnpwBlock({ block, items, selectedPerson }: RvwMnpwBlockProps) {
|
||||
});
|
||||
alert("배치되었습니다.");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "배치에 실패했습니다.";
|
||||
alert(message);
|
||||
alert(getErrorMessage(error, "배치에 실패했습니다."));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,8 +56,7 @@ function RvwMnpwBlock({ block, items, selectedPerson }: RvwMnpwBlockProps) {
|
||||
userUuid: item.userUuid,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "제거에 실패했습니다.";
|
||||
alert(message);
|
||||
alert(getErrorMessage(error, "제거에 실패했습니다."));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -119,6 +119,17 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeStateText(state: BadgeState) {
|
||||
switch (state) {
|
||||
case "completed":
|
||||
return "완료";
|
||||
case "current":
|
||||
return "진행중";
|
||||
case "pending":
|
||||
return "대기";
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
@@ -136,7 +147,9 @@ function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadg
|
||||
{badges.map((badge, index) => (
|
||||
<Fragment key={`${badge.label}-${index}`}>
|
||||
{index > 0 && <span className="mx-2">></span>}
|
||||
<Badge color={getBadgeColor(badge.state)}>{badge.label}</Badge>
|
||||
<Badge color={getBadgeColor(badge.state)}>
|
||||
{badge.label}({getBadgeStateText(badge.state)})
|
||||
</Badge>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ function ChecklistTaskPanel({
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption={`체크리스트 목록으로 검토항목, 주요 검토내용, 검토 결과${selectedReviewStep !== "LOCAL" ? ", 기술검토 의견" : ""} 정보 제공`} variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" rowSpan={2}>
|
||||
|
||||
@@ -35,7 +35,7 @@ function PreviousPlanAreaTable({ data }: PreviousPlanAreaTableProps) {
|
||||
const yearCells = getPlanYearCells(data);
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<Table caption="처리구역별 현황 목록으로 구분, 당초, 기준, 변경 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="16%" />
|
||||
<col width="9.3%" />
|
||||
@@ -86,7 +86,7 @@ function PreviousPlanAreaTable({ data }: PreviousPlanAreaTableProps) {
|
||||
const cells = getMetricCells(data, row.field);
|
||||
return (
|
||||
<TableRow key={row.field}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{cells.map((cell, index) => (
|
||||
<TableCell key={index} className="text-right">
|
||||
{cell}
|
||||
|
||||
@@ -202,7 +202,7 @@ function PreviousPlanFileSection({ clsfSystmNm }: PreviousPlanFileSectionProps)
|
||||
) : files.length === 0 ? (
|
||||
<p className="py-8 text-center text-lg text-muted-foreground">파일이 없습니다.</p>
|
||||
) : (
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본계획파일 목록으로 번호, 파일명, 등록일, 다운로드 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="!py-2 !text-xl">번호</TableHead>
|
||||
|
||||
@@ -13,7 +13,7 @@ function PreviousPlanSummaryTable({ data }: PreviousPlanSummaryTableProps) {
|
||||
const yearCells = getPlanYearCells(data);
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<Table caption="총설 목록으로 구분, 당초, 기준, 변경 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="10%" />
|
||||
@@ -54,7 +54,7 @@ function PreviousPlanSummaryTable({ data }: PreviousPlanSummaryTableProps) {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>계획목표년도</TableHead>
|
||||
<TableHead scope="row">계획목표년도</TableHead>
|
||||
{yearCells.map((cell, index) => (
|
||||
<TableCell key={index} className="text-center">
|
||||
{cell}
|
||||
|
||||
@@ -57,7 +57,7 @@ function BasicInfoFormSection() {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본계획 신청">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본계획 신청 입력으로 기본계획명, 계획년도, 기준년도, 1단계 목표연도, 2단계 목표연도, 3단계 목표연도, 4단계 목표연도, 계획개요, 구분, 신청 지자체 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -66,7 +66,7 @@ function BasicInfoFormSection() {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[20rem]" data-required>
|
||||
<TableHead scope="row" className="w-[20rem]" data-required>
|
||||
기본계획명
|
||||
</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
@@ -89,7 +89,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>계획년도</TableHead>
|
||||
<TableHead scope="row" data-required>계획년도</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.planYr"
|
||||
@@ -113,7 +113,7 @@ function BasicInfoFormSection() {
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead data-required>기준년도</TableHead>
|
||||
<TableHead scope="row" data-required>기준년도</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.crtrYr"
|
||||
@@ -139,7 +139,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>1단계 목표연도</TableHead>
|
||||
<TableHead scope="row" data-required>1단계 목표연도</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.frlvlGoalYr"
|
||||
@@ -166,7 +166,7 @@ function BasicInfoFormSection() {
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead data-required>2단계 목표연도</TableHead>
|
||||
<TableHead scope="row" data-required>2단계 목표연도</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.sclvlGoalYr"
|
||||
@@ -192,7 +192,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>3단계 목표연도</TableHead>
|
||||
<TableHead scope="row" data-required>3단계 목표연도</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.thlvlGoalYr"
|
||||
@@ -216,7 +216,7 @@ function BasicInfoFormSection() {
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableHead data-required>4단계 목표연도</TableHead>
|
||||
<TableHead scope="row" data-required>4단계 목표연도</TableHead>
|
||||
<TableCell>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.folvlGoalYr"
|
||||
@@ -242,7 +242,7 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>계획개요</TableHead>
|
||||
<TableHead scope="row" data-required>계획개요</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<Controller
|
||||
name="sewerBscPlanDtl.bizOtlnCn"
|
||||
@@ -264,9 +264,9 @@ function BasicInfoFormSection() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead scope="row">구분</TableHead>
|
||||
<TableCell>{sewerBscPlanSeLabel}</TableCell>
|
||||
<TableHead>신청 지자체</TableHead>
|
||||
<TableHead scope="row">신청 지자체</TableHead>
|
||||
<TableCell>{localGovLabel || userInfo?.laeSeNm}</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -18,7 +18,7 @@ function BasicInfoSection({ det, dtl }: BasicInfoSectionProps) {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="기본계획 신청">
|
||||
<Table variant="secondary">
|
||||
<Table caption="기본계획 신청으로 기본계획명, 계획년도, 기준년도, 1단계 목표연도, 2단계 목표연도, 3단계 목표연도, 4단계 목표연도, 계획개요, 구분, 신청 지자체 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -27,37 +27,37 @@ function BasicInfoSection({ det, dtl }: BasicInfoSectionProps) {
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>기본계획명</TableHead>
|
||||
<TableHead scope="row">기본계획명</TableHead>
|
||||
<TableCell colSpan={3}>{det.sewerBscPlanNm || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>계획년도</TableHead>
|
||||
<TableHead scope="row">계획년도</TableHead>
|
||||
<TableCell>{dtl?.planYr || "-"}</TableCell>
|
||||
<TableHead>기준년도</TableHead>
|
||||
<TableHead scope="row">기준년도</TableHead>
|
||||
<TableCell>{dtl?.crtrYr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>1단계 목표연도</TableHead>
|
||||
<TableHead scope="row">1단계 목표연도</TableHead>
|
||||
<TableCell>{dtl?.frlvlGoalYr || "-"}</TableCell>
|
||||
<TableHead>2단계 목표연도</TableHead>
|
||||
<TableHead scope="row">2단계 목표연도</TableHead>
|
||||
<TableCell>{dtl?.sclvlGoalYr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>3단계 목표연도</TableHead>
|
||||
<TableHead scope="row">3단계 목표연도</TableHead>
|
||||
<TableCell>{dtl?.thlvlGoalYr || "-"}</TableCell>
|
||||
<TableHead>4단계 목표연도</TableHead>
|
||||
<TableHead scope="row">4단계 목표연도</TableHead>
|
||||
<TableCell>{dtl?.folvlGoalYr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>계획개요</TableHead>
|
||||
<TableHead scope="row">계획개요</TableHead>
|
||||
<TableCell colSpan={3} className="whitespace-pre-wrap">
|
||||
{dtl?.bizOtlnCn || "-"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>구분</TableHead>
|
||||
<TableHead scope="row">구분</TableHead>
|
||||
<TableCell>{sewerBscPlanSeLabel || "-"}</TableCell>
|
||||
<TableHead>신청 지자체</TableHead>
|
||||
<TableHead scope="row">신청 지자체</TableHead>
|
||||
<TableCell>{localGovLabel || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
@@ -75,7 +75,7 @@ function BizCstMatrixFormSection() {
|
||||
<SectionCollapsible title="총사업비">
|
||||
<div className="overflow-x-auto">
|
||||
<p className="mb-2 text-right font-normal text-muted-foreground">(단위: 백만원)</p>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="총사업비 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -102,7 +102,7 @@ function BizCstMatrixFormSection() {
|
||||
|
||||
return (
|
||||
<TableRow key={item.code}>
|
||||
<TableHead className="text-left" style={{ paddingLeft }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft }}>
|
||||
<div className="relative">
|
||||
{showSubIcon && (
|
||||
<span className="absolute -left-6 top-0 text-gray-400">└</span>
|
||||
|
||||
@@ -62,7 +62,7 @@ function BizCstMatrixSection({ bizCstList, dtl }: BizCstMatrixSectionProps) {
|
||||
<SectionCollapsible title="총사업비">
|
||||
<div className="overflow-x-auto">
|
||||
<p className="mb-2 text-right font-normal text-muted-foreground">(단위: 백만원)</p>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="총사업비 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -89,7 +89,7 @@ function BizCstMatrixSection({ bizCstList, dtl }: BizCstMatrixSectionProps) {
|
||||
|
||||
return (
|
||||
<TableRow key={item.code}>
|
||||
<TableHead className="text-left" style={{ paddingLeft }}>
|
||||
<TableHead scope="row" className="text-left" style={{ paddingLeft }}>
|
||||
<div className="relative">
|
||||
{showSubIcon && (
|
||||
<span className="absolute -left-6 top-0 text-gray-400">└</span>
|
||||
|
||||
@@ -86,7 +86,13 @@ function ChecklistSection({
|
||||
>
|
||||
<TabsList>
|
||||
{visibleReviewSteps.map((stepCode) => (
|
||||
<TabsTrigger key={stepCode} value={stepCode} className="!px-6 !py-3 text-xl">
|
||||
<TabsTrigger
|
||||
key={stepCode}
|
||||
value={stepCode}
|
||||
id={`sewer-cklst-step-tab-${stepCode}`}
|
||||
aria-controls="sewer-cklst-task-tabs"
|
||||
className="!px-6 !py-3 text-xl"
|
||||
>
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
@@ -96,6 +102,7 @@ function ChecklistSection({
|
||||
|
||||
{taskCodes.length > 0 ? (
|
||||
<Tabs
|
||||
id="sewer-cklst-task-tabs"
|
||||
value={selectedTaskCode ?? taskCodes[0]}
|
||||
onValueChange={setSelectedTaskCode}
|
||||
className="w-full"
|
||||
@@ -107,6 +114,8 @@ function ChecklistSection({
|
||||
<TabsTrigger
|
||||
key={code}
|
||||
value={code}
|
||||
id={`sewer-cklst-tab-${code}`}
|
||||
aria-controls={`sewer-cklst-panel-${code}`}
|
||||
className="!px-6 !py-3 text-xl [&:after]:hidden"
|
||||
>
|
||||
{TASK_CODE_LABELS[code] ?? code}
|
||||
@@ -120,7 +129,15 @@ function ChecklistSection({
|
||||
</div>
|
||||
|
||||
{taskCodes.map((code) => (
|
||||
<TabsContent key={code} value={code} className="mt-0 border-0 p-0 shadow-none ring-0">
|
||||
<TabsContent
|
||||
key={code}
|
||||
value={code}
|
||||
id={`sewer-cklst-panel-${code}`}
|
||||
aria-labelledby={`sewer-cklst-tab-${code}`}
|
||||
className="mt-0 border-0 p-0 shadow-none ring-0"
|
||||
>
|
||||
{/* 선택된 탭(검토단계·업무구분)의 패널임을 알 수 있도록 숨김 제목 제공 */}
|
||||
<h3 className="sr-only">{CHECKLIST_TAB_LABELS[selectedReviewStep]} {TASK_CODE_LABELS[code] ?? code} 체크리스트</h3>
|
||||
<ChecklistTaskPanel
|
||||
selectedReviewStep={selectedReviewStep}
|
||||
selectedRows={getChecklistDisplayRows(cklstResultList, code)}
|
||||
|
||||
@@ -13,21 +13,21 @@ function ContactInfoSection({ dtl }: ContactInfoSectionProps) {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="담당자 정보">
|
||||
<Table variant="secondary">
|
||||
<Table caption="담당자 정보로 신청자(이름, 연락처, 소속, 이메일)과 설계자(이름, 연락처, 소속, 이메일) 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
{dtl.aplcntNm ? (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>신청자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>신청자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{dtl.aplcntNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{dtl.aplcntMblTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{dtl.aplcntOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{dtl.aplcntEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
@@ -35,16 +35,16 @@ function ContactInfoSection({ dtl }: ContactInfoSectionProps) {
|
||||
{dtl.dsgnrNm ? (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead rowSpan={2}>설계자</TableHead>
|
||||
<TableHead>이름</TableHead>
|
||||
<TableHead scope="row" rowSpan={2}>설계자</TableHead>
|
||||
<TableHead scope="row">이름</TableHead>
|
||||
<TableCell>{dtl.dsgnrNm || "-"}</TableCell>
|
||||
<TableHead>연락처</TableHead>
|
||||
<TableHead scope="row">연락처</TableHead>
|
||||
<TableCell>{dtl.dsgnrTelno || "-"}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead>소속</TableHead>
|
||||
<TableHead scope="row">소속</TableHead>
|
||||
<TableCell>{dtl.dsgnrOgdpNm || "-"}</TableCell>
|
||||
<TableHead>이메일</TableHead>
|
||||
<TableHead scope="row">이메일</TableHead>
|
||||
<TableCell>{dtl.dsgnrEmlAddr || "-"}</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
|
||||
@@ -85,7 +85,7 @@ function FnrsCstFormSection() {
|
||||
<h3 className="text-xl font-semibold">세부별 총사업비(①) - 재원분담 총사업비(②)</h3>
|
||||
<span className="font-normal text-muted-foreground">(단위: 백만원)</span>
|
||||
</div>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="세부별 총사업비(①) - 재원분담 총사업비(②) 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -107,7 +107,7 @@ function FnrsCstFormSection() {
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead>총사업비</TableHead>
|
||||
<TableHead scope="row">총사업비</TableHead>
|
||||
{remainingList.map((item) => (
|
||||
<TableCell key={item.planStpNo}>
|
||||
<div className={cn("text-right", item.remaining !== 0 ? "text-destructive" : "")}>
|
||||
@@ -125,7 +125,7 @@ function FnrsCstFormSection() {
|
||||
<h3 className="text-xl font-semibold">재원분담</h3>
|
||||
<span className="font-normal text-muted-foreground">(단위: 백만원)</span>
|
||||
</div>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="재원분담 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -148,7 +148,7 @@ function FnrsCstFormSection() {
|
||||
<TableBody>
|
||||
{FNRS_CST_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{PLAN_STEPS.map((planStpNo) => {
|
||||
const item = fnrsCstList.find((cst) => cst.planStpNo === planStpNo);
|
||||
return (
|
||||
|
||||
@@ -79,7 +79,7 @@ function FnrsCstSection({ fnrsCstList, dtl }: FnrsCstSectionProps) {
|
||||
<h3 className="text-xl font-semibold">세부별 총사업비(①) - 재원분담 총사업비(②)</h3>
|
||||
<span className="font-normal text-muted-foreground">(단위: 백만원)</span>
|
||||
</div>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="재원분담 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -102,7 +102,7 @@ function FnrsCstSection({ fnrsCstList, dtl }: FnrsCstSectionProps) {
|
||||
<TableBody>
|
||||
{FNRS_CST_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{stepHeaders.map((step) => {
|
||||
const item = matrixFnrsList.find((fnrs) => fnrs.planStpNo === step.planStpNo);
|
||||
return (
|
||||
|
||||
@@ -79,7 +79,7 @@ function OutlineFormSection() {
|
||||
이전기본계획조회
|
||||
</Button>
|
||||
</div>
|
||||
<Table variant="secondary">
|
||||
<Table caption="사업개요 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -102,7 +102,7 @@ function OutlineFormSection() {
|
||||
<TableBody>
|
||||
{OTLN_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{[1, 2, 3, 4].map((planStpNo) => {
|
||||
const item = otlnList.find((otln) => otln.planStpNo === planStpNo);
|
||||
return (
|
||||
|
||||
@@ -29,7 +29,7 @@ function OutlineSection({ otlnList, dtl }: OutlineSectionProps) {
|
||||
|
||||
return (
|
||||
<SectionCollapsible title="사업개요">
|
||||
<Table variant="secondary">
|
||||
<Table caption="사업개요 목록으로 구분, 1단계, 2단계, 3단계, 4단계 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="22%" />
|
||||
@@ -52,7 +52,7 @@ function OutlineSection({ otlnList, dtl }: OutlineSectionProps) {
|
||||
<TableBody>
|
||||
{OTLN_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{stepHeaders.map((step) => {
|
||||
const item = otlnList.find((otln) => otln.planStpNo === step.planStpNo);
|
||||
return (
|
||||
|
||||
@@ -187,7 +187,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-xl font-semibold">검토의견 작성</h4>
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 작성으로 의견내용, 검토결과, 첨부파일 정보 제공" variant="secondary">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[12rem]">의견내용</TableHead>
|
||||
@@ -308,7 +308,7 @@ const TechReviewOpinionSection = forwardRef<
|
||||
function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) {
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table variant="secondary">
|
||||
<Table caption="검토의견 목록으로 의견자명, 의견내용, 검토결과, 첨부파일, 등록일시 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="10%" />
|
||||
<col width="55%" />
|
||||
|
||||
@@ -120,6 +120,7 @@ function TmplntDtlInput({ facilityIndex, planStpNo, fieldKey, decimals }: Tmplnt
|
||||
|
||||
interface TmplntFacilityMatrixProps {
|
||||
swtrmtFcltCd: string;
|
||||
facilityName?: string;
|
||||
sido: string;
|
||||
gugun: string;
|
||||
facilityIndex: number;
|
||||
@@ -129,6 +130,7 @@ interface TmplntFacilityMatrixProps {
|
||||
|
||||
function TmplntFacilityMatrix({
|
||||
swtrmtFcltCd,
|
||||
facilityName,
|
||||
sido,
|
||||
gugun,
|
||||
facilityIndex,
|
||||
@@ -147,7 +149,10 @@ function TmplntFacilityMatrix({
|
||||
const originalData = Array.isArray(data) ? (data[0] ?? null) : null;
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<Table
|
||||
caption={`${facilityName ? `${facilityName} ` : ""}처리시설별 하수도정비기본계획 목록으로 구분, 당초, 현재, 변경 정보 제공`}
|
||||
variant="secondary"
|
||||
>
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="9.75%" />
|
||||
@@ -199,7 +204,7 @@ function TmplntFacilityMatrix({
|
||||
<TableBody>
|
||||
{TMPLNT_FIELD_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{PLAN_STP_NOS.map((planStpNo) => (
|
||||
<TableCell key={`original-${row.key}-${planStpNo}`} className="text-right">
|
||||
{getOriginalValue(originalData, row.key, planStpNo)}
|
||||
@@ -317,7 +322,10 @@ function TmplntFacilityAttachments({
|
||||
return (
|
||||
<div className="mt-6 space-y-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">처리구역 필수 첨부파일</h3>
|
||||
<Table variant="secondary">
|
||||
<Table
|
||||
caption={`${facilityName ? `${facilityName} ` : ""}처리구역 필수 첨부파일 목록으로 파일명, 파일 확장자, 파일첨부 정보 제공`}
|
||||
variant="secondary"
|
||||
>
|
||||
<colgroup>
|
||||
<col width="40%" />
|
||||
<col width="15%" />
|
||||
@@ -332,7 +340,7 @@ function TmplntFacilityAttachments({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableHead data-required align="left">
|
||||
<TableHead scope="row" data-required align="left">
|
||||
{facilityName} 계획평면도
|
||||
</TableHead>
|
||||
<TableCell className="text-center">{TMPLNT_ATCH_FORMAT_LABEL}</TableCell>
|
||||
@@ -481,6 +489,8 @@ function TmplntFormSection() {
|
||||
<TabsTrigger
|
||||
key={field.id}
|
||||
value={field.swtrmtFcltCd}
|
||||
id={`tmplnt-form-tab-${field.swtrmtFcltCd}`}
|
||||
aria-controls={`tmplnt-form-panel-${field.swtrmtFcltCd}`}
|
||||
className="px-6 py-3 text-2xl"
|
||||
>
|
||||
{facilityName}
|
||||
@@ -497,10 +507,14 @@ function TmplntFormSection() {
|
||||
<TabsContent
|
||||
key={field.id}
|
||||
value={field.swtrmtFcltCd}
|
||||
id={`tmplnt-form-panel-${field.swtrmtFcltCd}`}
|
||||
aria-labelledby={`tmplnt-form-tab-${field.swtrmtFcltCd}`}
|
||||
className="mt-0 border-0 p-0 shadow-none"
|
||||
>
|
||||
<h3 className="sr-only">{facilityName} 하수도정비기본계획</h3>
|
||||
<TmplntFacilityMatrix
|
||||
swtrmtFcltCd={field.swtrmtFcltCd}
|
||||
facilityName={facilityName}
|
||||
sido={sido ?? ""}
|
||||
gugun={gugun ?? ""}
|
||||
facilityIndex={facilityIndex}
|
||||
|
||||
@@ -77,6 +77,7 @@ function getDtlValue(
|
||||
|
||||
interface TmplntFacilityMatrixProps {
|
||||
item: SewerBscPlanTmplntResponseDto;
|
||||
facilityName?: string;
|
||||
sido?: string;
|
||||
gugun?: string;
|
||||
crtrYr?: string;
|
||||
@@ -85,6 +86,7 @@ interface TmplntFacilityMatrixProps {
|
||||
|
||||
function TmplntFacilityMatrix({
|
||||
item,
|
||||
facilityName,
|
||||
sido,
|
||||
gugun,
|
||||
crtrYr,
|
||||
@@ -102,7 +104,10 @@ function TmplntFacilityMatrix({
|
||||
const originalData = Array.isArray(data) ? (data[0] ?? null) : null;
|
||||
|
||||
return (
|
||||
<Table variant="secondary">
|
||||
<Table
|
||||
caption={`${facilityName ? `${facilityName} ` : ""}처리시설별 하수도정비기본계획 목록으로 구분, 당초, 현재, 변경 정보 제공`}
|
||||
variant="secondary"
|
||||
>
|
||||
<colgroup>
|
||||
<col width="12%" />
|
||||
<col width="9.75%" />
|
||||
@@ -154,7 +159,7 @@ function TmplntFacilityMatrix({
|
||||
<TableBody>
|
||||
{TMPLNT_FIELD_ROWS.map((row) => (
|
||||
<TableRow key={row.key}>
|
||||
<TableHead>{row.label}</TableHead>
|
||||
<TableHead scope="row">{row.label}</TableHead>
|
||||
{PLAN_STP_NOS.map((planStpNo) => (
|
||||
<TableCell key={`original-${row.key}-${planStpNo}`} className="text-right">
|
||||
{getOriginalValue(originalData, row.key, planStpNo)}
|
||||
@@ -217,6 +222,8 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
<TabsTrigger
|
||||
key={item.swtrmtFcltCd}
|
||||
value={item.swtrmtFcltCd ?? ""}
|
||||
id={`tmplnt-view-tab-${item.swtrmtFcltCd}`}
|
||||
aria-controls={`tmplnt-view-panel-${item.swtrmtFcltCd}`}
|
||||
className="!px-6 !py-3 text-xl"
|
||||
>
|
||||
{facilityName}
|
||||
@@ -228,10 +235,17 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
{tmplntList.map((item) => {
|
||||
const facilityName = resolveFacilityName(item, facilityOptions);
|
||||
return (
|
||||
<TabsContent key={item.swtrmtFcltCd} value={item.swtrmtFcltCd ?? ""}>
|
||||
<TabsContent
|
||||
key={item.swtrmtFcltCd}
|
||||
value={item.swtrmtFcltCd ?? ""}
|
||||
id={`tmplnt-view-panel-${item.swtrmtFcltCd}`}
|
||||
aria-labelledby={`tmplnt-view-tab-${item.swtrmtFcltCd}`}
|
||||
>
|
||||
<h3 className="sr-only">{facilityName} 하수도정비기본계획</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<TmplntFacilityMatrix
|
||||
item={item}
|
||||
facilityName={facilityName}
|
||||
sido={det.sido}
|
||||
gugun={det.gugun}
|
||||
crtrYr={dtl?.crtrYr}
|
||||
@@ -242,7 +256,10 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) {
|
||||
{(item.atchFileGroupId || item.atchFileInfo?.orgnlFileNm) && (
|
||||
<div className="mt-6 space-y-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">처리구역 필수 첨부파일</h3>
|
||||
<Table variant="secondary">
|
||||
<Table
|
||||
caption={`${facilityName ? `${facilityName} ` : ""}처리구역 필수 첨부파일 목록으로 파일명, 파일첨부 정보 제공`}
|
||||
variant="secondary"
|
||||
>
|
||||
<colgroup>
|
||||
<col width="35%" />
|
||||
<col width="65%" />
|
||||
|
||||
@@ -117,7 +117,7 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
{isPending ? (
|
||||
<Skeleton className="h-[16rem] w-full" />
|
||||
) : (
|
||||
<Table variant="secondary">
|
||||
<Table caption="하수도기본계획 신청으로 시도, 시군구, 계획구분, 업무구분 정보 제공" variant="secondary">
|
||||
<colgroup>
|
||||
<col width="15%" />
|
||||
<col width="35%" />
|
||||
@@ -127,7 +127,9 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
<TableHeader>
|
||||
{canSidoGugunSelectable && (
|
||||
<TableRow>
|
||||
<TableHead data-required>시도</TableHead>
|
||||
<TableHead scope="row" data-required>
|
||||
<label htmlFor="sewer-bsc-plan-apply-sido">시도</label>
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Select
|
||||
value={sidoComCd || undefined}
|
||||
@@ -136,7 +138,12 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
setSggComCd("");
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectTrigger
|
||||
id="sewer-bsc-plan-apply-sido"
|
||||
title="시도 선택"
|
||||
aria-required="true"
|
||||
className="w-full"
|
||||
>
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -148,14 +155,16 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableCell>
|
||||
<TableHead>시군구</TableHead>
|
||||
<TableHead scope="row">
|
||||
<label htmlFor="sewer-bsc-plan-apply-sgg">시군구</label>
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Select
|
||||
value={sggComCd || undefined}
|
||||
onValueChange={setSggComCd}
|
||||
disabled={!sidoComCd || isUserGugunPending}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectTrigger id="sewer-bsc-plan-apply-sgg" title="시군구 선택" className="w-full">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -170,12 +179,17 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableHead data-required>계획구분</TableHead>
|
||||
<TableHead id="sewer-bsc-plan-apply-plan-se-label" scope="row" data-required>
|
||||
계획구분
|
||||
</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<FieldSet className="px-2">
|
||||
{/* 라디오 그룹이 어떤 항목의 선택인지 알 수 있도록 th 와 연결 */}
|
||||
<RadioGroup
|
||||
value={sewerBscPlanSeComCd || undefined}
|
||||
onValueChange={setSewerBscPlanSeComCd}
|
||||
aria-labelledby="sewer-bsc-plan-apply-plan-se-label"
|
||||
aria-required="true"
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{planSeOptions?.map((option) => (
|
||||
@@ -199,10 +213,17 @@ function SewerBscPlanApplyDialog({ open, onOpenChange, onConfirm }: SewerBscPlan
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>업무구분</TableHead>
|
||||
<TableHead id="sewer-bsc-plan-apply-task-se-label" scope="row" data-required>
|
||||
업무구분
|
||||
</TableHead>
|
||||
<TableCell colSpan={3}>
|
||||
<FieldSet className="px-2">
|
||||
<RadioGroup value={taskSeComCd || undefined} onValueChange={setTaskSeComCd}>
|
||||
<RadioGroup
|
||||
value={taskSeComCd || undefined}
|
||||
onValueChange={setTaskSeComCd}
|
||||
aria-labelledby="sewer-bsc-plan-apply-task-se-label"
|
||||
aria-required="true"
|
||||
>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{taskSeOptions?.map((option) => (
|
||||
<FieldLabel
|
||||
|
||||
@@ -246,8 +246,11 @@ function SewerBscPlanFormPage({
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSave)} className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<div className="sticky top-[6rem] z-30 flex items-center justify-between bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<span className="text-red-600">*</span>은 필수입력 항목입니다.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{initialData?.detDto?.sewerBscPlanDocNo && (
|
||||
|
||||
@@ -63,6 +63,7 @@ function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: Sewe
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "하수도기본계획 목록으로 시도, 시군구, 기본계획명, 계획구분, 업무구분, 담당자, 해당기관, 진행상황, 등록일 정보 제공",
|
||||
viewPagination: true,
|
||||
onRowClick: handleRowClick,
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import { matchQueryStatus } from "./match-query-status";
|
||||
interface DataTableOptions<T> {
|
||||
variant?: "default" | "secondary" | "line";
|
||||
className?: HTMLAttributes<HTMLTableElement>["className"];
|
||||
caption?: string;
|
||||
viewPagination?: boolean;
|
||||
onRowClick?: (row: T) => void;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ function BnsPickerDialog({ open, onClose, onSelect }: BnsPickerDialogProps) {
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
caption: "사업찾기 목록으로 선택, 환경청, 시도, 시군구, 사업명, 예산신청년도, 사업시작년도, 사업종료년도, 총사업비 정보 제공",
|
||||
viewPagination: true,
|
||||
onRowClick: (row) => {
|
||||
setRowSelection({ [getBnsListRowKey(row)]: true });
|
||||
@@ -330,7 +331,7 @@ function BnsPickerDialog({ open, onClose, onSelect }: BnsPickerDialogProps) {
|
||||
|
||||
function BnsBasicInfoTable({ detail }: { detail: BnsDetailResponseDto }) {
|
||||
return (
|
||||
<Table variant="secondary" className="w-full">
|
||||
<Table caption="기본정보로 시도, 구군, 대권역, 단위유역, 중권역, 환경청, 회계구분, 단위사업, 세부사업, 내역사업, 세부내역, 세세부내역, 사업시작년도, 사업종료년도, 보조율(%), 사업상태, 사업명, 사업위치, 기본계획상의 목표년도, 사업추진현황, 상위계획, 기술진단, 사업내용, 비고 정보 제공" variant="secondary" className="w-full">
|
||||
<TableBody>
|
||||
<DetailPairRow
|
||||
leftLabel="시도"
|
||||
@@ -403,7 +404,7 @@ function BnsBasicInfoTable({ detail }: { detail: BnsDetailResponseDto }) {
|
||||
|
||||
function BnsOperationTable({ detail }: { detail: BnsDetailResponseDto }) {
|
||||
return (
|
||||
<Table variant="secondary" className="w-full">
|
||||
<Table caption="운영현황 및 사업효과로 유입하수처리장, 처리방식, 시설용량(현행), 시설용량(사업후), 처리구역면적(현행), 처리구역면적(사업후), 총인구(현행), 처리인구(현행), 처리인구(사업후), 하수관로, 배수설비 총가구수, 펌프장(개소), 방류수계, 방류하천, 사업후 예상유입하수량, 사업후 유입수질(예상BOD), 계획방류수질, 하천수질, 표준사업비, 기타사업물량 정보 제공" variant="secondary" className="w-full">
|
||||
<TableBody>
|
||||
<DetailPairRow
|
||||
leftLabel="유입하수처리장"
|
||||
|
||||
@@ -13,6 +13,9 @@ import {
|
||||
|
||||
import { type SelectedOgnzUser, toSelectedOgnzUser } from "../model/selected-ognz-user";
|
||||
|
||||
const FOCUS_RING_CLASS_NAME =
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring";
|
||||
|
||||
interface OgnzUserTreeNodeProps {
|
||||
node: OgnzUserTreeResponseDto;
|
||||
depth?: number;
|
||||
@@ -56,7 +59,10 @@ function OgnzUserTreeNode({
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group flex w-full items-center gap-2 rounded-sm p-2 text-left hover:bg-accent"
|
||||
className={cn(
|
||||
"group flex w-full items-center gap-2 rounded-sm p-2 text-left hover:bg-accent",
|
||||
FOCUS_RING_CLASS_NAME,
|
||||
)}
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
@@ -73,31 +79,51 @@ function OgnzUserTreeNode({
|
||||
{node.users.map((user) => {
|
||||
const isSelected = selectedUserId === user.userUnqId;
|
||||
const selectedUser = toSelectedOgnzUser(user, node);
|
||||
const rowClassName = cn(
|
||||
"group flex w-full cursor-pointer items-center gap-2 rounded-sm p-2 text-left hover:bg-accent",
|
||||
isSelected && "!border !border-primary !bg-primary-transparent",
|
||||
);
|
||||
const userLabel = (
|
||||
<>
|
||||
<span className="text-[1.4rem] text-label">{user.userNm}</span>
|
||||
<span className="text-xl text-muted-foreground">({user.jbpsSeNm})</span>
|
||||
</>
|
||||
);
|
||||
|
||||
// 선택 버튼이 없으면 행 자체를 버튼으로 렌더링해 키보드(Tab/Enter/Space)로 선택 가능하게 한다.
|
||||
if (!showSelectButton) {
|
||||
return (
|
||||
<button
|
||||
key={user.userUnqId}
|
||||
type="button"
|
||||
style={{ paddingInlineStart: indent + 24 }}
|
||||
className={cn(rowClassName, FOCUS_RING_CLASS_NAME)}
|
||||
aria-pressed={isSelected}
|
||||
onClick={() => onSelect(selectedUser)}
|
||||
>
|
||||
{userLabel}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={user.userUnqId}
|
||||
style={{ paddingInlineStart: indent + 24 }}
|
||||
className={cn(
|
||||
"group flex cursor-pointer items-center gap-2 rounded-sm p-2 hover:bg-accent",
|
||||
isSelected && "!border !border-primary !bg-primary-transparent",
|
||||
)}
|
||||
className={rowClassName}
|
||||
onClick={() => onSelect(selectedUser)}
|
||||
>
|
||||
<span className="text-[1.4rem] text-label">{user.userNm}</span>
|
||||
<span className="text-xl text-muted-foreground">({user.jbpsSeNm})</span>
|
||||
{showSelectButton ? (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect(selectedUser);
|
||||
}}
|
||||
>
|
||||
선택
|
||||
</Button>
|
||||
) : null}
|
||||
{userLabel}
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onSelect(selectedUser);
|
||||
}}
|
||||
>
|
||||
선택
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -380,7 +380,7 @@ function AttachmentsFormSection({
|
||||
{essentialFiles.length > 0 && (
|
||||
<div className="px-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<Table caption="필수 첨부파일 목록으로 파일명, 파일 확장자, 파일첨부 정보 제공" variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="44%" />
|
||||
<col width="12%" />
|
||||
@@ -402,6 +402,7 @@ function AttachmentsFormSection({
|
||||
return (
|
||||
<TableRow key={file.esntlAtchFileId}>
|
||||
<TableHead
|
||||
scope="row"
|
||||
className="group break-keep font-medium"
|
||||
data-required={file.esntlAtchYn === "Y"}
|
||||
align="left"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user