[as] feat: 설치인가 신청 시 업무구분 선택 팝업 추가

This commit is contained in:
2026-06-29 16:08:21 +09:00
parent fb160e712b
commit 20fd291cc3
7 changed files with 171 additions and 48 deletions
@@ -1,3 +1,14 @@
import { InstlRegistPage } from "@/pages/instl-sncn";
export default InstlRegistPage;
interface InstlSncnRegistRouteProps {
searchParams: Promise<{ taskSeComCd?: string }>;
}
export default async function Page({ searchParams }: InstlSncnRegistRouteProps) {
const params = await searchParams;
const taskSeComCd = params.taskSeComCd
? params.taskSeComCd.split(",").filter(Boolean)
: undefined;
return <InstlRegistPage taskSeComCd={taskSeComCd} />;
}
@@ -89,14 +89,17 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): CnfmRegistFo
};
}
export function getCreateDefaultValues(userInfo: IUserInfo | null): CnfmRegistFormData {
export function getCreateDefaultValues(
userInfo: IUserInfo | null,
taskSeComCd?: string[],
): CnfmRegistFormData {
return {
instlSncnMng: {
bizNm: "",
bizBgngYr: "",
bizEndYr: "",
bizCn: "",
taskSeComCd: [],
taskSeComCd: taskSeComCd ?? [],
sncnSeComCd: "",
dsgnrNm: "",
dsgnrTelno: "",
@@ -0,0 +1,107 @@
"use client";
import { useEffect, useState } from "react";
import { Field, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
import { useCodeList } from "@/entities/code";
import {
Button,
Checkbox,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
Skeleton,
Table,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@common/components";
interface TaskSePickerDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onConfirm: (selectedCodes: string[]) => void;
}
function TaskSePickerDialog({ open, onOpenChange, onConfirm }: TaskSePickerDialogProps) {
const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
const { data: taskOptions, isPending } = useCodeList("INSTL");
useEffect(() => {
if (!open) return;
setSelectedCodes([]);
}, [open]);
function handleCheckedChange(code: string, checked: boolean) {
setSelectedCodes((prev) =>
checked ? [...prev, code] : prev.filter((value) => value !== code),
);
}
function handleConfirm() {
onConfirm(selectedCodes);
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[60rem]" aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>설치인가 신청</DialogTitle>
</DialogHeader>
{isPending ? (
<Skeleton className="h-[16rem] w-full" />
) : (
<Table variant="secondary">
<TableHeader>
<TableRow>
<TableHead>업무구분</TableHead>
<TableCell>
<FieldSet className="px-2">
<FieldGroup data-slot="checkbox-group" className="flex-row flex-wrap">
{taskOptions?.map((option) => (
<Field key={option.lwpoCd} orientation="horizontal" className="w-auto">
<Checkbox
id={`task-se-picker-${option.lwpoCd}`}
checked={selectedCodes.includes(option.lwpoCd)}
onCheckedChange={(checked) =>
handleCheckedChange(option.lwpoCd, checked === true)
}
/>
<FieldLabel
htmlFor={`task-se-picker-${option.lwpoCd}`}
className="font-normal"
>
{option.lwpoCdNm}
</FieldLabel>
</Field>
))}
</FieldGroup>
</FieldSet>
</TableCell>
</TableRow>
</TableHeader>
</Table>
)}
<DialogFooter>
<Button type="button" color="white" onClick={() => onOpenChange(false)}>
취소
</Button>
<Button
type="button"
onClick={handleConfirm}
disabled={isPending || selectedCodes.length === 0}
>
확인
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export { TaskSePickerDialog };
@@ -28,9 +28,10 @@ import { PrivacyConsentSection } from "./sections/privacy-consent-section";
interface InstlFormPageProps {
mode: "create" | "edit";
initialData?: InstlSncnMngDetResponse;
taskSeComCd?: string[];
}
function InstlFormPage({ mode, initialData }: InstlFormPageProps) {
function InstlFormPage({ mode, initialData, taskSeComCd }: InstlFormPageProps) {
const router = useRouter();
const userInfo = useUserStore((state) => state.userInfo);
const alert = useAlertStore((state) => state.setMessage);
@@ -43,7 +44,7 @@ function InstlFormPage({ mode, initialData }: InstlFormPageProps) {
defaultValues:
isEditMode && initialData
? mapDetailToFormData(initialData)
: getCreateDefaultValues(userInfo),
: getCreateDefaultValues(userInfo, taskSeComCd),
});
async function onSave(data: CnfmRegistFormData) {
+27 -11
View File
@@ -1,6 +1,6 @@
"use client";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
@@ -11,6 +11,8 @@ import { Button } from "@common/components";
import { createInstlSncnColumns } from "../model/columns";
import { TaskSePickerDialog } from "./components/task-se-picker-dialog";
interface InstlListProps {
searchParams: InstlSncnMngSearchParams;
pagination: PaginationState;
@@ -19,6 +21,7 @@ interface InstlListProps {
function InstlList({ searchParams, pagination, onPaginationChange }: InstlListProps) {
const router = useRouter();
const [isTaskSeDialogOpen, setIsTaskSeDialogOpen] = useState(false);
const queryParams = useMemo(
() => ({
@@ -55,19 +58,32 @@ function InstlList({ searchParams, pagination, onPaginationChange }: InstlListPr
});
const handleRegisterClick = () => {
router.push("/instl-sncn/regist");
setIsTaskSeDialogOpen(true);
};
const handleConfirmTaskSe = (selectedCodes: string[]) => {
setIsTaskSeDialogOpen(false);
const query = selectedCodes.join(",");
router.push(`/instl-sncn/regist?taskSeComCd=${query}`);
};
return (
<section className="card card-border mx-0 bg-background pb-8 pt-0">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-semibold">설치인가 목록</h3>
<Button type="button" color="green" onClick={handleRegisterClick}>
신청
</Button>
</div>
{tableComponent}
</section>
<>
<section className="card card-border mx-0 bg-background pb-8 pt-0">
<div className="card-header flex items-center justify-between">
<h3 className="text-2xl font-semibold">설치인가 목록</h3>
<Button type="button" color="green" onClick={handleRegisterClick}>
신청
</Button>
</div>
{tableComponent}
</section>
<TaskSePickerDialog
open={isTaskSeDialogOpen}
onOpenChange={setIsTaskSeDialogOpen}
onConfirm={handleConfirmTaskSe}
/>
</>
);
}
@@ -1,7 +1,11 @@
import { InstlFormPage } from "./instl-form-page";
function InstlRegistPage() {
return <InstlFormPage mode="create" />;
interface InstlRegistPageProps {
taskSeComCd?: string[];
}
function InstlRegistPage({ taskSeComCd }: InstlRegistPageProps) {
return <InstlFormPage mode="create" taskSeComCd={taskSeComCd} />;
}
export { InstlRegistPage };
@@ -4,7 +4,7 @@ import { useState } from "react";
import { Controller, useFormContext } from "react-hook-form";
import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field";
import { useCodeList } from "@/entities/code";
import { findCodeLabel, useCodeList } from "@/entities/code";
import {
Button,
Checkbox,
@@ -41,6 +41,12 @@ function BasicInfoFormSection() {
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 variant="secondary">
@@ -139,36 +145,11 @@ function BasicInfoFormSection() {
<Controller
name="instlSncnMng.taskSeComCd"
control={control}
render={({ field, fieldState }) => (
<FieldSet>
<FieldGroup data-slot="checkbox-group" className="flex-row">
{taskOptions?.map((option) => (
<Field
key={option.lwpoCd}
orientation="horizontal"
data-invalid={fieldState.invalid}
className="w-auto"
>
<Checkbox
id={`task-${option.lwpoCd}`}
name={field.name}
aria-invalid={fieldState.invalid}
checked={field.value.includes(option.lwpoCd)}
onCheckedChange={(checked) => {
const newValue = checked
? [...field.value, option.lwpoCd]
: field.value.filter((value) => value !== option.lwpoCd);
field.onChange(newValue);
}}
/>
<FieldLabel htmlFor={`task-${option.lwpoCd}`} className="font-normal">
{option.lwpoCdNm}
</FieldLabel>
</Field>
))}
</FieldGroup>
render={({ fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<span>{taskSeComCdLabel}</span>
<FieldError errors={[fieldState.error]} />
</FieldSet>
</Field>
)}
/>
</TableCell>