[as] feat: 코드관리 페이지 구현
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { CodeManagementPage as default } from "@/pages/code-management";
|
||||
@@ -0,0 +1 @@
|
||||
export { CodeManagementPage } from "./ui/code-management-page";
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import type { AcntgSeMngResponseDto } from "@/entities/acntg";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<AcntgSeMngResponseDto>();
|
||||
|
||||
interface CreateAcntgSeColumnsOptions {
|
||||
onEdit: (row: AcntgSeMngResponseDto) => void;
|
||||
onDelete: (row: AcntgSeMngResponseDto) => void;
|
||||
}
|
||||
|
||||
function formatUseYn(value: string | undefined) {
|
||||
if (value === "Y") return "사용";
|
||||
if (value === "N") return "미사용";
|
||||
return "-";
|
||||
}
|
||||
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
export function createAcntgSeColumns({ onEdit, onDelete }: CreateAcntgSeColumnsOptions) {
|
||||
return [
|
||||
columnHelper.accessor("acntgSeCd", {
|
||||
header: "코드",
|
||||
meta: { align: "center" },
|
||||
}),
|
||||
columnHelper.accessor("acntgSeCdNm", {
|
||||
header: "코드명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("expln", {
|
||||
header: "설명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("useYn", {
|
||||
header: "사용여부",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatUseYn(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
header: "관리",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button type="button" size="sm" color="white" onClick={() => onEdit(row.original)}>
|
||||
수정
|
||||
</Button>
|
||||
<Button type="button" size="sm" color="red" onClick={() => onDelete(row.original)}>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
|
||||
import type { CnstpSeMngResponseDto } from "@/entities/cnstp";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
const columnHelper = createColumnHelper<CnstpSeMngResponseDto>();
|
||||
|
||||
interface CreateCnstpSeColumnsOptions {
|
||||
onEdit: (row: CnstpSeMngResponseDto) => void;
|
||||
onDelete: (row: CnstpSeMngResponseDto) => void;
|
||||
}
|
||||
|
||||
function formatUseYn(value: string | undefined) {
|
||||
if (value === "Y") return "사용";
|
||||
if (value === "N") return "미사용";
|
||||
return "-";
|
||||
}
|
||||
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
export function createCnstpSeColumns({ onEdit, onDelete }: CreateCnstpSeColumnsOptions) {
|
||||
return [
|
||||
columnHelper.accessor("cnstpSeCd", {
|
||||
header: "코드",
|
||||
meta: { align: "center" },
|
||||
}),
|
||||
columnHelper.accessor("cnstpSeCdNm", {
|
||||
header: "코드명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("expln", {
|
||||
header: "설명",
|
||||
meta: { align: "left" },
|
||||
cell: ({ getValue }) => formatOptional(getValue()),
|
||||
}),
|
||||
columnHelper.accessor("useYn", {
|
||||
header: "사용여부",
|
||||
meta: { align: "center" },
|
||||
cell: ({ getValue }) => formatUseYn(getValue()),
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
header: "관리",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button type="button" size="sm" color="white" onClick={() => onEdit(row.original)}>
|
||||
수정
|
||||
</Button>
|
||||
<Button type="button" size="sm" color="red" onClick={() => onDelete(row.original)}>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { useAcntgSeMng } from "@/entities/acntg";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Skeleton,
|
||||
} from "@common/components";
|
||||
|
||||
export interface AcntgSeFormValues {
|
||||
acntgSeCd: string;
|
||||
acntgSeCdNm: string;
|
||||
expln: string;
|
||||
useYn: "Y" | "N";
|
||||
}
|
||||
|
||||
interface AcntgSeFormDialogProps {
|
||||
mode: "create" | "edit";
|
||||
open: boolean;
|
||||
acntgSeCd?: string | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
isSubmitting?: boolean;
|
||||
onSubmit: (values: AcntgSeFormValues) => void;
|
||||
}
|
||||
|
||||
function AcntgSeFormDialog({
|
||||
mode,
|
||||
open,
|
||||
acntgSeCd,
|
||||
onOpenChange,
|
||||
isSubmitting,
|
||||
onSubmit,
|
||||
}: AcntgSeFormDialogProps) {
|
||||
const detailQuery = useAcntgSeMng(mode === "edit" ? acntgSeCd : null);
|
||||
|
||||
const { register, handleSubmit, reset, setValue, watch } = useForm<AcntgSeFormValues>({
|
||||
defaultValues: {
|
||||
acntgSeCd: "",
|
||||
acntgSeCdNm: "",
|
||||
expln: "",
|
||||
useYn: "Y",
|
||||
},
|
||||
});
|
||||
|
||||
const useYn = watch("useYn");
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
if (mode === "edit") {
|
||||
if (!detailQuery.data) return;
|
||||
reset({
|
||||
acntgSeCd: detailQuery.data.acntgSeCd,
|
||||
acntgSeCdNm: detailQuery.data.acntgSeCdNm ?? "",
|
||||
expln: detailQuery.data.expln ?? "",
|
||||
useYn: detailQuery.data.useYn === "N" ? "N" : "Y",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
reset({
|
||||
acntgSeCd: "",
|
||||
acntgSeCdNm: "",
|
||||
expln: "",
|
||||
useYn: "Y",
|
||||
});
|
||||
}, [open, mode, detailQuery.data, reset]);
|
||||
|
||||
const isLoading = mode === "edit" && detailQuery.isPending;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[52rem]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{mode === "create" ? "회계코드 등록" : "회계코드 수정"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{isLoading ? (
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[4rem]" />
|
||||
</div>
|
||||
) : (
|
||||
<form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="acntgSeCd">코드</Label>
|
||||
<Input
|
||||
id="acntgSeCd"
|
||||
placeholder="코드 입력"
|
||||
maxLength={10}
|
||||
readOnly={mode === "edit"}
|
||||
{...register("acntgSeCd", { required: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="acntgSeCdNm">코드명</Label>
|
||||
<Input
|
||||
id="acntgSeCdNm"
|
||||
placeholder="코드명 입력"
|
||||
maxLength={100}
|
||||
{...register("acntgSeCdNm", { required: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="expln">설명</Label>
|
||||
<Input id="expln" placeholder="설명 입력" maxLength={1000} {...register("expln")} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>사용여부</Label>
|
||||
<RadioGroup
|
||||
value={useYn}
|
||||
onValueChange={(v) => setValue("useYn", v as "Y" | "N")}
|
||||
className="flex gap-6"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="Y" id="acntg-use-y" />
|
||||
<Label htmlFor="acntg-use-y">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="N" id="acntg-use-n" />
|
||||
<Label htmlFor="acntg-use-n">미사용</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" color="white" onClick={() => onOpenChange(false)}>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { AcntgSeFormDialog };
|
||||
@@ -0,0 +1,144 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import {
|
||||
type AcntgSeMngResponseDto,
|
||||
useAcntgSeMng,
|
||||
useAcntgSeMngPage,
|
||||
useDeleteAcntgSeMng,
|
||||
useSaveAcntgSeMng,
|
||||
useUpdateAcntgSeMng,
|
||||
} from "@/entities/acntg";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { createAcntgSeColumns } from "../model/acntg-se-table-columns";
|
||||
|
||||
import { AcntgSeFormDialog, type AcntgSeFormValues } from "./acntg-se-form-dialog";
|
||||
|
||||
interface AcntgSeSectionProps {
|
||||
pagination: PaginationState;
|
||||
onPaginationChange: (pagination: PaginationState) => void;
|
||||
}
|
||||
|
||||
function AcntgSeSection({ pagination, onPaginationChange }: AcntgSeSectionProps) {
|
||||
const { confirm } = useConfirm();
|
||||
const [dialogMode, setDialogMode] = useState<"create" | "edit" | null>(null);
|
||||
const [editingCd, setEditingCd] = useState<string | null>(null);
|
||||
|
||||
const pageQuery = useAcntgSeMngPage({
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
|
||||
const detailQuery = useAcntgSeMng(dialogMode === "edit" ? editingCd : null);
|
||||
const saveMutation = useSaveAcntgSeMng();
|
||||
const updateMutation = useUpdateAcntgSeMng();
|
||||
const deleteMutation = useDeleteAcntgSeMng();
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (row: AcntgSeMngResponseDto) => {
|
||||
if (!(await confirm("삭제하시겠습니까?"))) return;
|
||||
await deleteMutation.mutateAsync(row.acntgSeCd);
|
||||
},
|
||||
[confirm, deleteMutation],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createAcntgSeColumns({
|
||||
onEdit: (row) => {
|
||||
setEditingCd(row.acntgSeCd);
|
||||
setDialogMode("edit");
|
||||
},
|
||||
onDelete: handleDelete,
|
||||
}),
|
||||
[handleDelete],
|
||||
);
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query: pageQuery,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: { pagination },
|
||||
onPaginationChange: (updater) => {
|
||||
const next = typeof updater === "function" ? updater(pagination) : updater;
|
||||
onPaginationChange(next);
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const handleFormSubmit = async (values: AcntgSeFormValues) => {
|
||||
if (!values.acntgSeCd.trim() || !values.acntgSeCdNm.trim()) return;
|
||||
|
||||
if (dialogMode === "create") {
|
||||
if (!(await confirm("저장하시겠습니까?"))) return;
|
||||
await saveMutation.mutateAsync({
|
||||
acntgSeCd: values.acntgSeCd.trim(),
|
||||
acntgSeCdNm: values.acntgSeCdNm.trim(),
|
||||
expln: values.expln.trim() || undefined,
|
||||
useYn: values.useYn,
|
||||
cdLvNo: 1,
|
||||
});
|
||||
} else if (dialogMode === "edit" && detailQuery.data) {
|
||||
if (!(await confirm("수정하시겠습니까?"))) return;
|
||||
await updateMutation.mutateAsync({
|
||||
acntgSeCd: detailQuery.data.acntgSeCd,
|
||||
body: {
|
||||
acntgSeCd: detailQuery.data.acntgSeCd,
|
||||
acntgSeCdNm: values.acntgSeCdNm.trim(),
|
||||
expln: values.expln.trim() || undefined,
|
||||
useYn: values.useYn,
|
||||
upAcntgSeCd: detailQuery.data.upAcntgSeCd,
|
||||
cdLvNo: detailQuery.data.cdLvNo,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setDialogMode(null);
|
||||
setEditingCd(null);
|
||||
};
|
||||
|
||||
const handleOpenCreate = () => {
|
||||
setEditingCd(null);
|
||||
setDialogMode("create");
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background pb-6 pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">회계코드 관리</h3>
|
||||
<Button type="button" onClick={handleOpenCreate}>
|
||||
등록
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
{dialogMode ? (
|
||||
<AcntgSeFormDialog
|
||||
mode={dialogMode}
|
||||
open={Boolean(dialogMode)}
|
||||
acntgSeCd={editingCd}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setDialogMode(null);
|
||||
setEditingCd(null);
|
||||
}
|
||||
}}
|
||||
isSubmitting={saveMutation.isPending || updateMutation.isPending}
|
||||
onSubmit={handleFormSubmit}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { AcntgSeSection };
|
||||
@@ -0,0 +1,151 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { useCnstpSeMng } from "@/entities/cnstp";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Skeleton,
|
||||
} from "@common/components";
|
||||
|
||||
export interface CnstpSeFormValues {
|
||||
cnstpSeCd: string;
|
||||
cnstpSeCdNm: string;
|
||||
expln: string;
|
||||
useYn: "Y" | "N";
|
||||
}
|
||||
|
||||
interface CnstpSeFormDialogProps {
|
||||
mode: "create" | "edit";
|
||||
open: boolean;
|
||||
cnstpSeCd?: string | null;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
isSubmitting?: boolean;
|
||||
onSubmit: (values: CnstpSeFormValues) => void;
|
||||
}
|
||||
|
||||
function CnstpSeFormDialog({
|
||||
mode,
|
||||
open,
|
||||
cnstpSeCd,
|
||||
onOpenChange,
|
||||
isSubmitting,
|
||||
onSubmit,
|
||||
}: CnstpSeFormDialogProps) {
|
||||
const detailQuery = useCnstpSeMng(mode === "edit" ? cnstpSeCd : null);
|
||||
|
||||
const { register, handleSubmit, reset, setValue, watch } = useForm<CnstpSeFormValues>({
|
||||
defaultValues: {
|
||||
cnstpSeCd: "",
|
||||
cnstpSeCdNm: "",
|
||||
expln: "",
|
||||
useYn: "Y",
|
||||
},
|
||||
});
|
||||
|
||||
const useYn = watch("useYn");
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
if (mode === "edit") {
|
||||
if (!detailQuery.data) return;
|
||||
reset({
|
||||
cnstpSeCd: detailQuery.data.cnstpSeCd,
|
||||
cnstpSeCdNm: detailQuery.data.cnstpSeCdNm ?? "",
|
||||
expln: detailQuery.data.expln ?? "",
|
||||
useYn: detailQuery.data.useYn === "N" ? "N" : "Y",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
reset({
|
||||
cnstpSeCd: "",
|
||||
cnstpSeCdNm: "",
|
||||
expln: "",
|
||||
useYn: "Y",
|
||||
});
|
||||
}, [open, mode, detailQuery.data, reset]);
|
||||
|
||||
const isLoading = mode === "edit" && detailQuery.isPending;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[52rem]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{mode === "create" ? "공종코드 등록" : "공종코드 수정"}</DialogTitle>
|
||||
</DialogHeader>
|
||||
{isLoading ? (
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[4rem]" />
|
||||
<Skeleton className="h-[4rem]" />
|
||||
</div>
|
||||
) : (
|
||||
<form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cnstpSeCd">코드</Label>
|
||||
<Input
|
||||
id="cnstpSeCd"
|
||||
placeholder="코드 입력"
|
||||
maxLength={10}
|
||||
readOnly={mode === "edit"}
|
||||
{...register("cnstpSeCd", { required: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cnstpSeCdNm">코드명</Label>
|
||||
<Input
|
||||
id="cnstpSeCdNm"
|
||||
placeholder="코드명 입력"
|
||||
maxLength={100}
|
||||
{...register("cnstpSeCdNm")}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="expln">설명</Label>
|
||||
<Input id="expln" placeholder="설명 입력" maxLength={1000} {...register("expln")} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>사용여부</Label>
|
||||
<RadioGroup
|
||||
value={useYn}
|
||||
onValueChange={(v) => setValue("useYn", v as "Y" | "N")}
|
||||
className="flex gap-6"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="Y" id="cnstp-use-y" />
|
||||
<Label htmlFor="cnstp-use-y">사용</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<RadioGroupItem value="N" id="cnstp-use-n" />
|
||||
<Label htmlFor="cnstp-use-n">미사용</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="button" color="white" onClick={() => onOpenChange(false)}>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting}>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { CnstpSeFormDialog };
|
||||
@@ -0,0 +1,147 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import {
|
||||
type CnstpSeMngResponseDto,
|
||||
useCnstpSeMng,
|
||||
useCnstpSeMngPage,
|
||||
useDeleteCnstpSeMng,
|
||||
useSaveCnstpSeMng,
|
||||
useUpdateCnstpSeMng,
|
||||
} from "@/entities/cnstp";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { createCnstpSeColumns } from "../model/cnstp-se-table-columns";
|
||||
|
||||
import { CnstpSeFormDialog, type CnstpSeFormValues } from "./cnstp-se-form-dialog";
|
||||
|
||||
const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
interface CnstpSeSectionProps {
|
||||
pagination: PaginationState;
|
||||
onPaginationChange: (pagination: PaginationState) => void;
|
||||
}
|
||||
|
||||
function CnstpSeSection({ pagination, onPaginationChange }: CnstpSeSectionProps) {
|
||||
const { confirm } = useConfirm();
|
||||
const [dialogMode, setDialogMode] = useState<"create" | "edit" | null>(null);
|
||||
const [editingCd, setEditingCd] = useState<string | null>(null);
|
||||
|
||||
const pageQuery = useCnstpSeMngPage({
|
||||
page: pagination.pageIndex,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
|
||||
const detailQuery = useCnstpSeMng(dialogMode === "edit" ? editingCd : null);
|
||||
const saveMutation = useSaveCnstpSeMng();
|
||||
const updateMutation = useUpdateCnstpSeMng();
|
||||
const deleteMutation = useDeleteCnstpSeMng();
|
||||
|
||||
const handleDelete = useCallback(
|
||||
async (row: CnstpSeMngResponseDto) => {
|
||||
if (!(await confirm("삭제하시겠습니까?"))) return;
|
||||
await deleteMutation.mutateAsync(row.cnstpSeCd);
|
||||
},
|
||||
[confirm, deleteMutation],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createCnstpSeColumns({
|
||||
onEdit: (row) => {
|
||||
setEditingCd(row.cnstpSeCd);
|
||||
setDialogMode("edit");
|
||||
},
|
||||
onDelete: handleDelete,
|
||||
}),
|
||||
[handleDelete],
|
||||
);
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
query: pageQuery,
|
||||
tableOptions: {
|
||||
columns,
|
||||
state: { pagination },
|
||||
onPaginationChange: (updater) => {
|
||||
const next = typeof updater === "function" ? updater(pagination) : updater;
|
||||
onPaginationChange(next);
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
},
|
||||
dataTableOptions: {
|
||||
viewPagination: true,
|
||||
},
|
||||
});
|
||||
|
||||
const handleFormSubmit = async (values: CnstpSeFormValues) => {
|
||||
if (!values.cnstpSeCd.trim()) return;
|
||||
|
||||
if (dialogMode === "create") {
|
||||
if (!(await confirm("저장하시겠습니까?"))) return;
|
||||
await saveMutation.mutateAsync({
|
||||
cnstpSeCd: values.cnstpSeCd.trim(),
|
||||
cnstpSeCdNm: values.cnstpSeCdNm.trim() || undefined,
|
||||
expln: values.expln.trim() || undefined,
|
||||
useYn: values.useYn,
|
||||
cdLvNo: 1,
|
||||
});
|
||||
} else if (dialogMode === "edit" && detailQuery.data) {
|
||||
if (!(await confirm("수정하시겠습니까?"))) return;
|
||||
await updateMutation.mutateAsync({
|
||||
cnstpSeCd: detailQuery.data.cnstpSeCd,
|
||||
body: {
|
||||
cnstpSeCd: detailQuery.data.cnstpSeCd,
|
||||
cnstpSeCdNm: values.cnstpSeCdNm.trim() || undefined,
|
||||
expln: values.expln.trim() || undefined,
|
||||
useYn: values.useYn,
|
||||
upCnstpCd: detailQuery.data.upCnstpCd,
|
||||
cdLvNo: detailQuery.data.cdLvNo,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setDialogMode(null);
|
||||
setEditingCd(null);
|
||||
};
|
||||
|
||||
const handleOpenCreate = () => {
|
||||
setEditingCd(null);
|
||||
setDialogMode("create");
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="card card-border mx-0 bg-background pb-6 pt-0">
|
||||
<div className="card-header flex items-center justify-between">
|
||||
<h3 className="text-2xl font-semibold">공종코드 관리</h3>
|
||||
<Button type="button" onClick={handleOpenCreate}>
|
||||
등록
|
||||
</Button>
|
||||
</div>
|
||||
{tableComponent}
|
||||
{dialogMode ? (
|
||||
<CnstpSeFormDialog
|
||||
mode={dialogMode}
|
||||
open={Boolean(dialogMode)}
|
||||
cnstpSeCd={editingCd}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setDialogMode(null);
|
||||
setEditingCd(null);
|
||||
}
|
||||
}}
|
||||
isSubmitting={saveMutation.isPending || updateMutation.isPending}
|
||||
onSubmit={handleFormSubmit}
|
||||
/>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export { CnstpSeSection, DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE };
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { AcntgSeSection } from "./acntg-se-section";
|
||||
import { CnstpSeSection, DEFAULT_PAGE_INDEX, DEFAULT_PAGE_SIZE } from "./cnstp-se-section";
|
||||
|
||||
function CodeManagementPage() {
|
||||
const [cnstpPagination, setCnstpPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [acntgPagination, setAcntgPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="space-y-6 px-[1.8rem] pb-6">
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
||||
<CnstpSeSection pagination={cnstpPagination} onPaginationChange={setCnstpPagination} />
|
||||
<AcntgSeSection pagination={acntgPagination} onPaginationChange={setAcntgPagination} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { CodeManagementPage };
|
||||
Reference in New Issue
Block a user