Files
frontend/apps/as/src/pages/instl-sncn/ui/sections/basic-info-form-section.tsx
T
2026-10-11 13:16:56 +09:00

304 lines
12 KiB
TypeScript

"use client";
import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { NON_NEGATIVE_INPUT_PROPS } from "@/shared/lib";
import { SectionCollapsible } from "@/shared/ui";
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
import { findCodeLabel, useCodeList } from "@/entities/code";
import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker";
import {
Button,
Checkbox,
Input,
RadioGroup,
RadioGroupItem,
Table,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@common/components";
import type { InstlSncnRegistFormData } from "../../lib/instl-sncn-regist-schema";
function BasicInfoFormSection() {
const { control, watch, setValue } = useFormContext<InstlSncnRegistFormData>();
const [isPickerOpen, setIsPickerOpen] = useState(false);
const taskSeComCd = watch("instlSncnMng.taskSeComCd");
const localGovLabel = [watch("instlSncnMng.sido"), watch("instlSncnMng.gugun")]
.filter(Boolean)
.join(" ");
function handleBnsSelect(data: BnsApplyData) {
setValue("instlSncnMng.bizNm", data.bizNm);
setValue("instlSncnMng.bizBgngYr", data.bizBgngYr);
setValue("instlSncnMng.bizEndYr", data.bizEndYr);
setValue("instlSncnMng.bizCn", data.bizCn);
setValue("instlSncnMng.bgtBizCd", data.bgtBizCd);
setValue("instlSncnMng.bgtAplyYr", data.bgtAplyYr);
}
const { data: taskOptions } = useCodeList("INSTL");
const { data: sncnOptions } = useCodeList("SNCNSE");
const { data: rtlztnFcltOptions } = useCodeList("RTLZTNFCLTSE");
const { data: rtlztnUsgOptions } = useCodeList("RTLZTNUSGSE");
const taskSeComCdLabel =
taskSeComCd
.map((code) => findCodeLabel(taskOptions, code))
.filter(Boolean)
.join(", ") || "-";
return (
<SectionCollapsible title="기본 정보">
<Table caption="기본 정보 입력으로 사업명, 사업기간, 사업내용, 구분, 신청 지자체, 사업구분, 시설종류, 재이용용도 정보 제공" variant="secondary">
<colgroup>
<col width="15%" />
<col width="35%" />
<col width="15%" />
<col width="35%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead scope="row" className="w-[20rem]" data-required>
사업명
</TableHead>
<TableCell colSpan={3}>
<Controller
name="instlSncnMng.bizNm"
control={control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<div className="flex gap-2">
<Input
{...field}
id={field.name}
placeholder="사업명 입력"
aria-invalid={fieldState.invalid}
className="w-[40rem]"
/>
<Button type="button" onClick={() => setIsPickerOpen(true)}>
사업찾기
</Button>
</div>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row" data-required>사업기간</TableHead>
<TableCell colSpan={3}>
<FieldGroup className="flex-row items-center gap-2">
<Controller
name="instlSncnMng.bizBgngYr"
control={control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-auto !gap-1">
<Input
{...field}
{...NON_NEGATIVE_INPUT_PROPS}
type="number"
id={field.name}
placeholder="시작연도"
className="max-w-[12rem]"
aria-invalid={fieldState.invalid}
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
<span>~</span>
<Controller
name="instlSncnMng.bizEndYr"
control={control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid} className="w-auto !gap-1">
<Input
{...field}
{...NON_NEGATIVE_INPUT_PROPS}
type="number"
id={field.name}
placeholder="종료연도"
className="max-w-[12rem]"
aria-invalid={fieldState.invalid}
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
</FieldGroup>
</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row" data-required>사업내용</TableHead>
<TableCell colSpan={3}>
<Controller
name="instlSncnMng.bizCn"
control={control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<Input
{...field}
id={field.name}
placeholder="사업내용 입력"
aria-invalid={fieldState.invalid}
/>
<FieldError errors={[fieldState.error]} />
</Field>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row" data-required>구분</TableHead>
<TableCell>{taskSeComCdLabel}</TableCell>
<TableHead scope="row">신청 지자체</TableHead>
<TableCell>{localGovLabel}</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row" data-required>사업구분</TableHead>
<TableCell colSpan={3}>
<Controller
name="instlSncnMng.sncnSeComCd"
control={control}
render={({ field, fieldState }) => (
<FieldSet>
<RadioGroup
name={field.name}
value={field.value}
onValueChange={field.onChange}
>
{sncnOptions?.map((option) => (
<FieldLabel
key={option.lwpoCd}
htmlFor={`sncn-${option.lwpoCd}`}
className="!w-auto border-none"
>
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="w-auto"
>
<RadioGroupItem
value={option.lwpoCd}
id={`sncn-${option.lwpoCd}`}
aria-invalid={fieldState.invalid}
/>
<span className="font-normal">{option.lwpoCdNm}</span>
</Field>
</FieldLabel>
))}
</RadioGroup>
<FieldError errors={[fieldState.error]} />
</FieldSet>
)}
/>
</TableCell>
</TableRow>
{taskSeComCd.includes("RE") && (
<>
<TableRow>
<TableHead scope="row">시설종류</TableHead>
<TableCell colSpan={3}>
<Controller
name="instlSncnMng.rtlztnFcltSeComCd"
control={control}
render={({ field, fieldState }) => (
<FieldSet>
<RadioGroup
name={field.name}
value={field.value || ""}
onValueChange={field.onChange}
>
{rtlztnFcltOptions?.map((option) => (
<FieldLabel
key={option.lwpoCd}
htmlFor={`rtlztn-fclt-${option.lwpoCd}`}
className="!w-auto border-none"
>
<Field
orientation="horizontal"
data-invalid={fieldState.invalid}
className="w-auto"
>
<RadioGroupItem
value={option.lwpoCd}
id={`rtlztn-fclt-${option.lwpoCd}`}
aria-invalid={fieldState.invalid}
/>
<span className="font-normal">{option.lwpoCdNm}</span>
</Field>
</FieldLabel>
))}
</RadioGroup>
<FieldError errors={[fieldState.error]} />
</FieldSet>
)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableHead scope="row">재이용용도</TableHead>
<TableCell colSpan={3}>
<Controller
name="instlSncnMng.rtlztnUsgSeComCd"
control={control}
render={({ field, fieldState }) => (
<FieldSet>
<FieldGroup data-slot="checkbox-group" className="flex-row">
{rtlztnUsgOptions?.map((option) => (
<Field
key={option.lwpoCd}
orientation="horizontal"
data-invalid={fieldState.invalid}
className="w-auto"
>
<Checkbox
id={`rtlztn-usg-${option.lwpoCd}`}
name={field.name}
aria-invalid={fieldState.invalid}
checked={field.value?.includes(option.lwpoCd) ?? false}
onCheckedChange={(checked) => {
const currentValue = field.value || "";
const values = currentValue ? currentValue.split(",") : [];
const newValue = checked
? [...values, option.lwpoCd]
: values.filter((value) => value !== option.lwpoCd);
field.onChange(newValue.join(","));
}}
/>
<FieldLabel
htmlFor={`rtlztn-usg-${option.lwpoCd}`}
className="font-normal"
>
{option.lwpoCdNm}
</FieldLabel>
</Field>
))}
</FieldGroup>
<FieldError errors={[fieldState.error]} />
</FieldSet>
)}
/>
</TableCell>
</TableRow>
</>
)}
</TableHeader>
</Table>
<BnsPickerDialog
open={isPickerOpen}
onClose={() => setIsPickerOpen(false)}
onSelect={handleBnsSelect}
/>
</SectionCollapsible>
);
}
export { BasicInfoFormSection };